Move command registry from components/command/ into components/core/. New modules: mod_canbus, mod_honeypot, mod_fallback, mod_redteam, mod_ota. Replace mod_proxy with tun_core (multiplexed SOCKS5 tunnel). Kconfig extended with per-module settings and async worker config.
358 lines
12 KiB
C
358 lines
12 KiB
C
/*
|
|
* canbus_obd.c
|
|
* OBD-II PID decoder with lookup table for ~40 common PIDs.
|
|
*
|
|
* Uses ISO-TP for communication (even single-frame OBD fits in SF,
|
|
* but VIN and DTC responses may require multi-frame).
|
|
*/
|
|
#include "sdkconfig.h"
|
|
|
|
#if defined(CONFIG_MODULE_CANBUS) && defined(CONFIG_CANBUS_OBD)
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "esp_log.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/semphr.h"
|
|
|
|
#include "canbus_obd.h"
|
|
#include "canbus_isotp.h"
|
|
#include "canbus_driver.h"
|
|
#include "utils.h"
|
|
|
|
#define TAG "CAN_OBD"
|
|
|
|
/* ============================================================
|
|
* PID Decoder Table
|
|
* ============================================================ */
|
|
|
|
typedef float (*decode_fn_t)(const uint8_t *data, int len);
|
|
|
|
typedef struct {
|
|
uint8_t pid;
|
|
const char *name;
|
|
const char *unit;
|
|
int data_bytes; /* Expected response data bytes (A, AB, ABC...) */
|
|
decode_fn_t decode;
|
|
} pid_decoder_t;
|
|
|
|
/* Decode functions — all check buffer length before access */
|
|
static float decode_a(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0]; }
|
|
static float decode_a_minus_40(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0] - 40.0f; }
|
|
static float decode_a_percent(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0] * 100.0f / 255.0f; }
|
|
static float decode_ab(const uint8_t *d, int len) { if (len < 2) return 0.0f; return (float)((d[0] << 8) | d[1]); }
|
|
static float decode_ab_div_4(const uint8_t *d, int len) { if (len < 2) return 0.0f; return (float)((d[0] << 8) | d[1]) / 4.0f; }
|
|
static float decode_a_div_2_m64(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0] / 2.0f - 64.0f; }
|
|
static float decode_ab_div_100(const uint8_t *d, int len) { if (len < 2) return 0.0f; return (float)((d[0] << 8) | d[1]) / 100.0f; }
|
|
static float decode_a_x3(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0] * 3.0f; }
|
|
static float decode_ab_div_20(const uint8_t *d, int len) { if (len < 2) return 0.0f; return (float)((d[0] << 8) | d[1]) / 20.0f; }
|
|
static float decode_signed_a_minus_128(const uint8_t *d, int len) { if (len < 1) return 0.0f; return (float)d[0] - 128.0f; }
|
|
|
|
static const pid_decoder_t s_pid_table[] = {
|
|
/* PID Name Unit Bytes Decoder */
|
|
{ 0x04, "Engine Load", "%", 1, decode_a_percent },
|
|
{ 0x05, "Coolant Temp", "C", 1, decode_a_minus_40 },
|
|
{ 0x06, "Short Fuel Trim B1", "%", 1, decode_signed_a_minus_128 },
|
|
{ 0x07, "Long Fuel Trim B1", "%", 1, decode_signed_a_minus_128 },
|
|
{ 0x0B, "Intake MAP", "kPa", 1, decode_a },
|
|
{ 0x0C, "Engine RPM", "rpm", 2, decode_ab_div_4 },
|
|
{ 0x0D, "Vehicle Speed", "km/h", 1, decode_a },
|
|
{ 0x0E, "Timing Advance", "deg", 1, decode_a_div_2_m64 },
|
|
{ 0x0F, "Intake Temp", "C", 1, decode_a_minus_40 },
|
|
{ 0x10, "MAF Rate", "g/s", 2, decode_ab_div_100 },
|
|
{ 0x11, "Throttle Position", "%", 1, decode_a_percent },
|
|
{ 0x1C, "OBD Standard", "", 1, decode_a },
|
|
{ 0x1F, "Engine Runtime", "s", 2, decode_ab },
|
|
{ 0x21, "Distance w/ MIL", "km", 2, decode_ab },
|
|
{ 0x2C, "Commanded EGR", "%", 1, decode_a_percent },
|
|
{ 0x2F, "Fuel Level", "%", 1, decode_a_percent },
|
|
{ 0x30, "Warmups since DTC clear", "", 1, decode_a },
|
|
{ 0x31, "Distance since DTC clear", "km", 2, decode_ab },
|
|
{ 0x33, "Baro Pressure", "kPa", 1, decode_a },
|
|
{ 0x42, "Control Module Voltage", "V", 2, decode_ab_div_100 }, /* Approx */
|
|
{ 0x45, "Relative Throttle", "%", 1, decode_a_percent },
|
|
{ 0x46, "Ambient Temp", "C", 1, decode_a_minus_40 },
|
|
{ 0x49, "Accelerator Position D", "%", 1, decode_a_percent },
|
|
{ 0x4A, "Accelerator Position E", "%", 1, decode_a_percent },
|
|
{ 0x4C, "Commanded Throttle", "%", 1, decode_a_percent },
|
|
{ 0x5C, "Oil Temp", "C", 1, decode_a_minus_40 },
|
|
{ 0x5E, "Fuel Rate", "L/h", 2, decode_ab_div_20 },
|
|
{ 0x67, "Coolant Temp (wide)", "C", 1, decode_a_minus_40 }, /* First byte only */
|
|
{ 0xA6, "Odometer", "km", 2, decode_ab }, /* Simplified */
|
|
};
|
|
|
|
#define PID_TABLE_SIZE (sizeof(s_pid_table) / sizeof(s_pid_table[0]))
|
|
|
|
/* Find decoder for a PID */
|
|
static const pid_decoder_t *find_pid(uint8_t pid)
|
|
{
|
|
for (int i = 0; i < (int)PID_TABLE_SIZE; i++) {
|
|
if (s_pid_table[i].pid == pid) return &s_pid_table[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* ============================================================
|
|
* OBD-II Communication (via ISO-TP)
|
|
* ============================================================ */
|
|
|
|
/* Send OBD request and receive response */
|
|
static int obd_transact(uint8_t mode, uint8_t pid,
|
|
uint8_t *resp, size_t resp_cap, size_t *resp_len)
|
|
{
|
|
uint8_t req[2] = { mode, pid };
|
|
|
|
/* Use functional broadcast (0x7DF) for Mode 01/03/09 */
|
|
/* Listen on first responder (0x7E8) — most vehicles respond here */
|
|
isotp_status_t st = isotp_request(
|
|
OBD_REQUEST_ID, OBD_RESPONSE_MIN,
|
|
req, 2,
|
|
resp, resp_cap, resp_len,
|
|
2000
|
|
);
|
|
|
|
return (st == ISOTP_OK) ? 0 : -1;
|
|
}
|
|
|
|
/* ============================================================
|
|
* Public API
|
|
* ============================================================ */
|
|
|
|
int obd_query_pid(uint8_t mode, uint8_t pid, obd_result_t *out)
|
|
{
|
|
if (!out) return -1;
|
|
memset(out, 0, sizeof(*out));
|
|
|
|
uint8_t resp[16];
|
|
size_t resp_len = 0;
|
|
|
|
if (obd_transact(mode, pid, resp, sizeof(resp), &resp_len) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
/* Response: mode+0x40, PID, data bytes */
|
|
if (resp_len < 2 || resp[0] != (mode + 0x40) || resp[1] != pid) {
|
|
return -1;
|
|
}
|
|
|
|
out->pid = pid;
|
|
|
|
/* Try to decode with known PID table */
|
|
const pid_decoder_t *dec = find_pid(pid);
|
|
if (dec) {
|
|
out->name = dec->name;
|
|
out->unit = dec->unit;
|
|
int data_offset = 2; /* After mode+0x40 and PID */
|
|
int data_avail = (int)resp_len - data_offset;
|
|
if (data_avail >= dec->data_bytes) {
|
|
out->value = dec->decode(&resp[data_offset], data_avail);
|
|
}
|
|
} else {
|
|
out->name = "Unknown";
|
|
out->unit = "";
|
|
out->value = (resp_len > 2) ? (float)resp[2] : 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int obd_query_supported(uint8_t pids_out[], int max_pids)
|
|
{
|
|
int total = 0;
|
|
uint8_t resp[16];
|
|
size_t resp_len = 0;
|
|
|
|
/* PID 00: supported PIDs 01-20 */
|
|
/* PID 20: supported PIDs 21-40 */
|
|
/* PID 40: supported PIDs 41-60 */
|
|
/* PID 60: supported PIDs 61-80 */
|
|
|
|
uint8_t range_pids[] = { 0x00, 0x20, 0x40, 0x60 };
|
|
|
|
for (int r = 0; r < 4; r++) {
|
|
if (obd_transact(0x01, range_pids[r], resp, sizeof(resp), &resp_len) < 0) {
|
|
break;
|
|
}
|
|
|
|
if (resp_len < 6 || resp[0] != 0x41 || resp[1] != range_pids[r]) {
|
|
break;
|
|
}
|
|
|
|
/* 4 bytes = 32 bits, each bit = supported PID */
|
|
uint32_t bitmap = ((uint32_t)resp[2] << 24)
|
|
| ((uint32_t)resp[3] << 16)
|
|
| ((uint32_t)resp[4] << 8)
|
|
| (uint32_t)resp[5];
|
|
|
|
for (int bit = 0; bit < 32 && total < max_pids; bit++) {
|
|
if (bitmap & (1U << (31 - bit))) {
|
|
pids_out[total++] = range_pids[r] + bit + 1;
|
|
}
|
|
}
|
|
|
|
/* If last PID in range is not supported, no point checking next range */
|
|
if (!(bitmap & 0x01)) break;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
int obd_read_vin(char *vin_out, size_t cap)
|
|
{
|
|
if (!vin_out || cap < 18) return -1;
|
|
|
|
uint8_t req[2] = { 0x09, 0x02 }; /* Mode 09, PID 02 = VIN */
|
|
uint8_t resp[64];
|
|
size_t resp_len = 0;
|
|
|
|
isotp_status_t st = isotp_request(
|
|
OBD_REQUEST_ID, OBD_RESPONSE_MIN,
|
|
req, 2,
|
|
resp, sizeof(resp), &resp_len,
|
|
3000
|
|
);
|
|
|
|
if (st != ISOTP_OK) return -1;
|
|
|
|
/* Response: 0x49, 0x02, count, VIN (17 ASCII chars) */
|
|
if (resp_len < 20 || resp[0] != 0x49 || resp[1] != 0x02) {
|
|
return -1;
|
|
}
|
|
|
|
/* VIN starts at offset 3 (after 0x49, 0x02, count) */
|
|
int vin_start = 3;
|
|
int vin_len = (int)resp_len - vin_start;
|
|
if (vin_len > 17) vin_len = 17;
|
|
if (vin_len > (int)cap - 1) vin_len = (int)cap - 1;
|
|
|
|
memcpy(vin_out, &resp[vin_start], vin_len);
|
|
vin_out[vin_len] = '\0';
|
|
|
|
return 0;
|
|
}
|
|
|
|
int obd_read_dtcs(char *dtc_buf, size_t cap)
|
|
{
|
|
uint8_t req[1] = { 0x03 }; /* Mode 03: Request DTCs */
|
|
uint8_t resp[128];
|
|
size_t resp_len = 0;
|
|
|
|
isotp_status_t st = isotp_request(
|
|
OBD_REQUEST_ID, OBD_RESPONSE_MIN,
|
|
req, 1,
|
|
resp, sizeof(resp), &resp_len,
|
|
3000
|
|
);
|
|
|
|
if (st != ISOTP_OK || resp_len < 1 || resp[0] != 0x43) {
|
|
snprintf(dtc_buf, cap, "No DTCs or read error");
|
|
return -1;
|
|
}
|
|
|
|
int num_dtcs = resp[1]; /* Number of DTCs */
|
|
if (num_dtcs == 0) {
|
|
snprintf(dtc_buf, cap, "No DTCs stored");
|
|
return 0;
|
|
}
|
|
|
|
int off = 0;
|
|
off += snprintf(dtc_buf + off, cap - off, "DTCs (%d): ", num_dtcs);
|
|
|
|
/* Each DTC is 2 bytes, starting at offset 2 */
|
|
static const char dtc_prefixes[] = { 'P', 'C', 'B', 'U' };
|
|
|
|
for (int i = 0; i < num_dtcs && (2 + i * 2 + 1) < (int)resp_len; i++) {
|
|
uint16_t raw = (resp[2 + i * 2] << 8) | resp[2 + i * 2 + 1];
|
|
|
|
char prefix = dtc_prefixes[(raw >> 14) & 0x03];
|
|
int code = raw & 0x3FFF;
|
|
|
|
off += snprintf(dtc_buf + off, cap - off, "%c%04X ", prefix, code);
|
|
if (off >= (int)cap - 8) break;
|
|
}
|
|
|
|
return off;
|
|
}
|
|
|
|
/* ============================================================
|
|
* Continuous Monitoring
|
|
* ============================================================ */
|
|
|
|
static volatile bool s_monitor_running = false;
|
|
static TaskHandle_t s_monitor_task = NULL;
|
|
static uint8_t s_monitor_pids[16];
|
|
static int s_monitor_pid_count = 0;
|
|
static int s_monitor_interval = 1000;
|
|
static const char *s_monitor_req_id = NULL;
|
|
static SemaphoreHandle_t s_mon_mutex = NULL;
|
|
|
|
static void monitor_task(void *arg)
|
|
{
|
|
(void)arg;
|
|
ESP_LOGI(TAG, "OBD monitor started: %d PIDs, %d ms interval",
|
|
s_monitor_pid_count, s_monitor_interval);
|
|
|
|
while (s_monitor_running) {
|
|
for (int i = 0; i < s_monitor_pid_count && s_monitor_running; i++) {
|
|
obd_result_t result;
|
|
if (obd_query_pid(0x01, s_monitor_pids[i], &result) == 0) {
|
|
char line[96];
|
|
snprintf(line, sizeof(line), "OBD|%s|%.1f|%s",
|
|
result.name, result.value, result.unit);
|
|
msg_data(TAG, line, strlen(line), false, s_monitor_req_id);
|
|
}
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(s_monitor_interval));
|
|
}
|
|
|
|
ESP_LOGI(TAG, "OBD monitor stopped");
|
|
s_monitor_task = NULL;
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
void obd_monitor_start(const uint8_t *pids, int pid_count,
|
|
int interval_ms, const char *request_id)
|
|
{
|
|
if (!s_mon_mutex) s_mon_mutex = xSemaphoreCreateMutex();
|
|
xSemaphoreTake(s_mon_mutex, portMAX_DELAY);
|
|
|
|
if (s_monitor_running) {
|
|
xSemaphoreGive(s_mon_mutex);
|
|
obd_monitor_stop();
|
|
xSemaphoreTake(s_mon_mutex, portMAX_DELAY);
|
|
}
|
|
|
|
if (pid_count > 16) pid_count = 16;
|
|
memcpy(s_monitor_pids, pids, pid_count);
|
|
s_monitor_pid_count = pid_count;
|
|
s_monitor_interval = (interval_ms > 0) ? interval_ms : 1000;
|
|
s_monitor_req_id = request_id;
|
|
s_monitor_running = true;
|
|
|
|
xTaskCreatePinnedToCore(
|
|
monitor_task, "obd_mon", 4096, NULL, 3, &s_monitor_task, 1
|
|
);
|
|
|
|
xSemaphoreGive(s_mon_mutex);
|
|
}
|
|
|
|
void obd_monitor_stop(void)
|
|
{
|
|
if (!s_mon_mutex) s_mon_mutex = xSemaphoreCreateMutex();
|
|
xSemaphoreTake(s_mon_mutex, portMAX_DELAY);
|
|
s_monitor_running = false;
|
|
xSemaphoreGive(s_mon_mutex);
|
|
|
|
for (int i = 0; i < 20 && s_monitor_task != NULL; i++) {
|
|
vTaskDelay(pdMS_TO_TICKS(50));
|
|
}
|
|
}
|
|
|
|
bool obd_monitor_is_running(void)
|
|
{
|
|
return s_monitor_running;
|
|
}
|
|
|
|
#endif /* CONFIG_MODULE_CANBUS && CONFIG_CANBUS_OBD */
|