Phase 03: real CAM UPER codec + ESP32-C5 TX/RX serial link
- Firmware: rewrite obu-firmware TX loop to be serial-driven (no on-chip timer), add promiscuous RX + GeoNetworking/BTP unwrap (gn_unwrap.c), add binary UART framing to the phone (serial_link.c/.h). Drop local cam_encode() - CAM is now built on the phone. - Kotlin: byte-exact UPER CAM encoder/decoder ported from cam.c (BitWriter/BitReader/CamUperCodec), matching SerialFrame codec, real UsbSerialTransport (usb-serial-for-android), CamTransmitLoop (1Hz base rate, event/geofence boost, ESP32-C5-only), wired into CamUseCaseRepository for RX and TripRecordingService for TX. - Add V2X message retention: persist all CAM (own+remote) to Room while recording, drop otherwise (DB v2 -> v3 migration). - Add jitpack repo + usb-serial-for-android dependency. Fixes: UsbSerialTransport now uses SerialInputOutputManager.start()/stop() (this lib version manages its own thread internally) instead of manual Runnable/Thread submission, which didn't compile.
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
#include "serial_link.h"
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "serial_link";
|
||||
|
||||
#define SYNC0 0xAA
|
||||
#define SYNC1 0x55
|
||||
|
||||
static serial_link_cam_tx_cb_t s_on_cam_tx;
|
||||
|
||||
// ---- CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF, no reflect, no xorout) ----
|
||||
// Bytewise (no table) - frames here are at most SERIAL_LINK_MAX_PAYLOAD + 3 bytes, so table
|
||||
// lookup isn't worth the flash/RAM tradeoff. MUST match the Kotlin-side implementation exactly
|
||||
// (see app SerialFrame.kt) or every frame will be silently rejected as corrupt.
|
||||
static uint16_t crc16_ccitt_false(const uint8_t *data, size_t len)
|
||||
{
|
||||
uint16_t crc = 0xFFFF;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
crc ^= (uint16_t)data[i] << 8;
|
||||
for (int b = 0; b < 8; b++) {
|
||||
crc = (crc & 0x8000) ? (uint16_t)((crc << 1) ^ 0x1021) : (uint16_t)(crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static bool send_frame(uint8_t type, const uint8_t *payload, int len)
|
||||
{
|
||||
if (len < 0 || len > SERIAL_LINK_MAX_PAYLOAD) {
|
||||
ESP_LOGW(TAG, "send_frame: payload too large (%d)", len);
|
||||
return false;
|
||||
}
|
||||
|
||||
// type(1) + length(2) + payload(len) is what the CRC covers.
|
||||
uint8_t head[3];
|
||||
head[0] = type;
|
||||
head[1] = (uint8_t)(len & 0xFF);
|
||||
head[2] = (uint8_t)((len >> 8) & 0xFF);
|
||||
|
||||
uint16_t crc;
|
||||
{
|
||||
// Compute CRC over head+payload without a combined buffer copy: CRC is a running
|
||||
// state, so feed it in two calls worth of bytes by concatenating into a small stack
|
||||
// buffer (payload is capped at SERIAL_LINK_MAX_PAYLOAD, so head+payload comfortably
|
||||
// fits on the stack).
|
||||
uint8_t crc_buf[3 + SERIAL_LINK_MAX_PAYLOAD];
|
||||
memcpy(crc_buf, head, 3);
|
||||
if (len > 0) memcpy(crc_buf + 3, payload, (size_t)len);
|
||||
crc = crc16_ccitt_false(crc_buf, (size_t)(3 + len));
|
||||
}
|
||||
|
||||
uint8_t sync[2] = {SYNC0, SYNC1};
|
||||
uint8_t crc_bytes[2] = {(uint8_t)(crc & 0xFF), (uint8_t)((crc >> 8) & 0xFF)};
|
||||
|
||||
// Four separate writes rather than one assembled buffer - simplest given payload is
|
||||
// already wherever the caller has it (avoids a second copy of up to 160 bytes).
|
||||
int wrote = 0;
|
||||
wrote += uart_write_bytes(SERIAL_LINK_UART_NUM, sync, sizeof(sync));
|
||||
wrote += uart_write_bytes(SERIAL_LINK_UART_NUM, head, sizeof(head));
|
||||
if (len > 0) wrote += uart_write_bytes(SERIAL_LINK_UART_NUM, payload, (size_t)len);
|
||||
wrote += uart_write_bytes(SERIAL_LINK_UART_NUM, crc_bytes, sizeof(crc_bytes));
|
||||
|
||||
return wrote == (int)(sizeof(sync) + sizeof(head) + len + sizeof(crc_bytes));
|
||||
}
|
||||
|
||||
bool serial_link_send_cam_rx(int8_t rssi, const uint8_t *cam_uper, int cam_len)
|
||||
{
|
||||
if (cam_len < 0 || cam_len > SERIAL_LINK_MAX_PAYLOAD - 1) {
|
||||
ESP_LOGW(TAG, "send_cam_rx: cam_len too large (%d)", cam_len);
|
||||
return false;
|
||||
}
|
||||
uint8_t payload[SERIAL_LINK_MAX_PAYLOAD];
|
||||
payload[0] = (uint8_t)rssi;
|
||||
memcpy(payload + 1, cam_uper, (size_t)cam_len);
|
||||
return send_frame(SERIAL_MSG_CAM_RX, payload, 1 + cam_len);
|
||||
}
|
||||
|
||||
bool serial_link_send_status(uint8_t status)
|
||||
{
|
||||
return send_frame(SERIAL_MSG_STATUS, &status, 1);
|
||||
}
|
||||
|
||||
// ---- RX framing state machine ----
|
||||
// Runs in its own task, byte-at-a-time off the UART driver's RX ring buffer (via
|
||||
// uart_read_bytes with a short timeout, not raw ISR access - simplest correct option for a
|
||||
// link this slow/small; revisit if CAM traffic volume ever makes this a bottleneck).
|
||||
typedef enum {
|
||||
WAIT_SYNC0,
|
||||
WAIT_SYNC1,
|
||||
WAIT_TYPE,
|
||||
WAIT_LEN_LO,
|
||||
WAIT_LEN_HI,
|
||||
WAIT_PAYLOAD,
|
||||
WAIT_CRC_LO,
|
||||
WAIT_CRC_HI,
|
||||
} rx_state_t;
|
||||
|
||||
static void rx_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
rx_state_t state = WAIT_SYNC0;
|
||||
uint8_t type = 0;
|
||||
uint16_t len = 0;
|
||||
uint16_t payload_idx = 0;
|
||||
uint8_t payload[SERIAL_LINK_MAX_PAYLOAD];
|
||||
uint16_t crc_recv = 0;
|
||||
|
||||
uint8_t byte;
|
||||
while (1) {
|
||||
int n = uart_read_bytes(SERIAL_LINK_UART_NUM, &byte, 1, pdMS_TO_TICKS(50));
|
||||
if (n <= 0) continue;
|
||||
|
||||
switch (state) {
|
||||
case WAIT_SYNC0:
|
||||
state = (byte == SYNC0) ? WAIT_SYNC1 : WAIT_SYNC0;
|
||||
break;
|
||||
case WAIT_SYNC1:
|
||||
state = (byte == SYNC1) ? WAIT_TYPE : (byte == SYNC0 ? WAIT_SYNC1 : WAIT_SYNC0);
|
||||
break;
|
||||
case WAIT_TYPE:
|
||||
type = byte;
|
||||
state = WAIT_LEN_LO;
|
||||
break;
|
||||
case WAIT_LEN_LO:
|
||||
len = byte;
|
||||
state = WAIT_LEN_HI;
|
||||
break;
|
||||
case WAIT_LEN_HI:
|
||||
len |= (uint16_t)byte << 8;
|
||||
if (len > SERIAL_LINK_MAX_PAYLOAD) {
|
||||
ESP_LOGW(TAG, "rx: length %u exceeds max, resyncing", len);
|
||||
state = WAIT_SYNC0; // can't trust this frame boundary at all - drop to resync
|
||||
} else if (len == 0) {
|
||||
payload_idx = 0;
|
||||
state = WAIT_CRC_LO;
|
||||
} else {
|
||||
payload_idx = 0;
|
||||
state = WAIT_PAYLOAD;
|
||||
}
|
||||
break;
|
||||
case WAIT_PAYLOAD:
|
||||
payload[payload_idx++] = byte;
|
||||
if (payload_idx >= len) state = WAIT_CRC_LO;
|
||||
break;
|
||||
case WAIT_CRC_LO:
|
||||
crc_recv = byte;
|
||||
state = WAIT_CRC_HI;
|
||||
break;
|
||||
case WAIT_CRC_HI: {
|
||||
crc_recv |= (uint16_t)byte << 8;
|
||||
|
||||
uint8_t crc_buf[3 + SERIAL_LINK_MAX_PAYLOAD];
|
||||
crc_buf[0] = type;
|
||||
crc_buf[1] = (uint8_t)(len & 0xFF);
|
||||
crc_buf[2] = (uint8_t)((len >> 8) & 0xFF);
|
||||
if (len > 0) memcpy(crc_buf + 3, payload, len);
|
||||
uint16_t crc_calc = crc16_ccitt_false(crc_buf, (size_t)(3 + len));
|
||||
|
||||
if (crc_calc == crc_recv) {
|
||||
if (type == SERIAL_MSG_CAM_TX && s_on_cam_tx) {
|
||||
s_on_cam_tx(payload, len);
|
||||
} else if (type != SERIAL_MSG_CAM_TX) {
|
||||
ESP_LOGW(TAG, "rx: unexpected frame type 0x%02x from phone, ignoring", type);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "rx: CRC mismatch (got %04x want %04x), dropping frame", crc_recv, crc_calc);
|
||||
}
|
||||
state = WAIT_SYNC0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void serial_link_init(serial_link_cam_tx_cb_t on_cam_tx)
|
||||
{
|
||||
s_on_cam_tx = on_cam_tx;
|
||||
|
||||
uart_config_t cfg = {
|
||||
.baud_rate = SERIAL_LINK_BAUD,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
ESP_ERROR_CHECK(uart_driver_install(SERIAL_LINK_UART_NUM, 1024, 1024, 0, NULL, 0));
|
||||
ESP_ERROR_CHECK(uart_param_config(SERIAL_LINK_UART_NUM, &cfg));
|
||||
ESP_ERROR_CHECK(uart_set_pin(SERIAL_LINK_UART_NUM, SERIAL_LINK_TX_GPIO, SERIAL_LINK_RX_GPIO,
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
|
||||
xTaskCreate(rx_task, "serial_link_rx", 4096, NULL, 6, NULL);
|
||||
ESP_LOGI(TAG, "serial_link up on UART%d, TX=GPIO%d RX=GPIO%d @ %d baud",
|
||||
SERIAL_LINK_UART_NUM, SERIAL_LINK_TX_GPIO, SERIAL_LINK_RX_GPIO, SERIAL_LINK_BAUD);
|
||||
}
|
||||
Reference in New Issue
Block a user