#ifndef SERIAL_LINK_H #define SERIAL_LINK_H #include #include #include // Binary framing for the phone <-> ESP32-C5 link (Phase 03). Deliberately NOT the same UART as // the ESP-IDF console/ESP_LOG output (UART0, see sdkconfig CONFIG_ESP_CONSOLE_UART_NUM=0) - // mixing binary frames with human-readable log text on one wire would corrupt both. This runs // on a dedicated UART (see SERIAL_LINK_UART_NUM / TX / RX pins below - CHANGE THESE to match // your board's actual wiring from the USB-C connector's UART bridge to ESP32-C5 GPIOs). // // Frame format (both directions, symmetric): // [0xAA][0x55][type:1][length:2 LE][payload: length bytes][crc16:2 LE] // CRC16 is CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF, no reflect, no xorout), computed over // type + length + payload only (not the two sync bytes). Same algorithm must be used on the // Kotlin side (see app SerialFrame.kt) - frames that don't checksum are silently dropped. // // Direction / types: // SERIAL_MSG_CAM_TX (0x01), phone -> ESP32: payload is a raw CAM UPER byte string, already // built by the phone (position/speed/heading/yaw rate baked in). On receipt the ESP32 // immediately GeoNetworking-wraps and transmits it - this IS the transmit clock now, there // is no independent on-chip timer. See main.c's rx-driven tx path. // SERIAL_MSG_CAM_RX (0x02), ESP32 -> phone: payload is [rssi:1 signed][CAM UPER bytes...] - a // CAM received over the air, already stripped of its 802.11/LLC-SNAP/GeoNetworking/BTP-B // framing by gn_unwrap.c. The phone never sees raw 802.11 frames. No station id is carried // separately - CAM's own ItsPduHeader.stationID (the first field inside the UPER bytes) is // already the meaningful identifier; see gn_unwrap.h for why a second one isn't added here. // SERIAL_MSG_STATUS (0x03), ESP32 -> phone: 1-byte heartbeat (0 = ok), sent periodically so // the phone can distinguish "link idle" from "link dead" independent of CAM traffic. #define SERIAL_MSG_CAM_TX 0x01 #define SERIAL_MSG_CAM_RX 0x02 #define SERIAL_MSG_STATUS 0x03 // CHANGE THESE to match your board's actual USB-C -> UART bridge wiring. UART0 is already // claimed by the console/ESP_LOG; picking UART1 here to stay clear of it. These are common // free GPIOs on ESP32-C5 devkits but are NOT guaranteed free on your specific board - check // your schematic before flashing. #define SERIAL_LINK_UART_NUM 1 #define SERIAL_LINK_TX_GPIO 4 #define SERIAL_LINK_RX_GPIO 5 #define SERIAL_LINK_BAUD 115200 // Max CAM payload this link will carry. cam.c sizes its own encode buffer at 96 bytes; 160 // gives headroom for the RX path's extra station_id+rssi prefix plus margin. #define SERIAL_LINK_MAX_PAYLOAD 160 // Initializes the dedicated UART and its background RX-framing task. Call once from app_main, // after nvs/event loop init. `on_cam_tx` is invoked (from the RX task's context - keep it fast, // it blocks the next frame's parsing) whenever a complete, checksummed SERIAL_MSG_CAM_TX frame // arrives from the phone. typedef void (*serial_link_cam_tx_cb_t)(const uint8_t *cam_uper, int cam_len); void serial_link_init(serial_link_cam_tx_cb_t on_cam_tx); // Sends a SERIAL_MSG_CAM_RX frame to the phone: rssi + the CAM UPER bytes gn_unwrap.c extracted // from an over-the-air frame. Returns true if the frame was written to the UART (not an // end-to-end ack - the phone may still drop it, e.g. serial buffer overrun). bool serial_link_send_cam_rx(int8_t rssi, const uint8_t *cam_uper, int cam_len); // Sends a 1-byte SERIAL_MSG_STATUS heartbeat frame. bool serial_link_send_status(uint8_t status); #endif