DENM over-the-air receive on the ESP32-C5 path
The firmware forwarded CAM only: gn_unwrap_cam accepted single-hop broadcast (HT=5) and BTP port 2001, so every DENM was dropped before it reached the phone. Real OBUs disseminate DENM by GeoBroadcast (HT=4), whose 44-byte extended header also carries the hazard's relevance area - materially more useful on a map than the sender's own position, since a sender may be relaying for someone else. Firmware - gn_unwrap_cam -> gn_unwrap_its: accepts GeoBroadcast alongside TSB/SHB, and BTP ports 2001 and 2002, extracting the GeoBroadcast destination area. Both extended-header lengths were measured against live air capture rather than read off a spec table. Secured packets (Basic Header NextHeader=2) are rejected rather than misparsed. - SERIAL_MSG_CAM_RX (0x02) superseded by SERIAL_MSG_V2X_RX (0x04): a 14-byte prefix carrying BTP port, RSSI and the destination area. Adding MAPEM later needs a decoder on the phone but no protocol change. 0x02 stays reserved so the numbering is not silently reused. - Promiscuous RX capture buffer 400 -> 800 bytes. A real GeoBroadcast DENM is around 500 bytes on air and was being truncated mid-payload, which no amount of correct unwrapping downstream could have recovered from. - geonet_wrap_shb, both firmwares: the SHB extended header is 28 bytes, not 24. The Source Position Vector is followed by a 4-byte reserved field; without it a standards-strict receiver reads the CAM payload's first two bytes as the BTP destination port. App - DenmUperCodec: UPER decoder for the ManagementContainer and the SituationContainer's eventType. ValidityDuration is 17 bits, not 16, and ManagementContainer, SituationContainer and CauseCode each carry their own extension bit - a single wrong bit made a real frame read causeCode 47 instead of 94. - DenmEvent gains actionID (originatingStationID + sequenceNumber), stationType, termination, detectionTime, relevance radius and RSSI. Dedup keys on actionID where available, so a termination lands on the event it ends instead of creating a second pin. - denmEvents merges the MQTT and over-the-air sources and drops terminated events. The V2X list view now shows hazards above the CAM stations; it previously took no DENM parameter at all, so hazards reached the map but never the list. - DenmParser: the Use Case API sends causeCode as a string enum, so reading it as an Int always yielded null. Testing - DenmAirReceiveTest covers the V2X_RX prefix and the decoder using real frames from a live capture as fixtures. Expected values were cross-checked against the ETSI ASN.1 modules via asn1tools, which agreed on all 1885 decodable DENMs across the capture set, every field including detectionTime. - Verified on hardware: a CiT One HLN-SV DENM decodes as cause 94/0 with a 1000 m relevance radius at 1 Hz alongside CAM, with no decode failures and no unexpected BTP ports. Also replaces em dashes with hyphens throughout the user-facing strings, including the German translation.
This commit is contained in:
@@ -112,23 +112,47 @@ static bool send_frame(uint8_t type, const uint8_t *payload, int len)
|
||||
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)
|
||||
bool serial_link_send_v2x_rx(uint16_t btp_dest_port, int8_t rssi,
|
||||
bool has_geo_area,
|
||||
int32_t geo_area_lat_tenmicrodeg,
|
||||
int32_t geo_area_lon_tenmicrodeg,
|
||||
uint16_t geo_area_distance_a_m,
|
||||
const uint8_t *uper, int uper_len)
|
||||
{
|
||||
if (cam_len < 0 || cam_len > SERIAL_LINK_MAX_PAYLOAD - 1) {
|
||||
if (uper_len < 0 || uper_len > SERIAL_LINK_MAX_PAYLOAD - SERIAL_V2X_RX_PREFIX_LEN) {
|
||||
// Counted, not just logged: this log line goes to the flashing port, which nobody is
|
||||
// watching during a phone bench session - so the symptom would be "that station just
|
||||
// never shows up in the app" with no visible cause.
|
||||
bump(&s_oversize_drops);
|
||||
ESP_LOGW(TAG, "send_cam_rx: cam_len too large (%d), total oversize drops %u",
|
||||
cam_len, s_oversize_drops);
|
||||
ESP_LOGW(TAG, "send_v2x_rx: port %u payload too large (%d), total oversize drops %u",
|
||||
btp_dest_port, uper_len, s_oversize_drops);
|
||||
return false;
|
||||
}
|
||||
// static, not stack (515 bytes at MAX_PAYLOAD 512); only rx_forward_task calls this, and
|
||||
// static, not stack (526 bytes at MAX_PAYLOAD 512); only rx_forward_task calls this, and
|
||||
// send_frame's mutex covers the handoff onto the wire.
|
||||
static uint8_t s_cam_rx_payload[SERIAL_LINK_MAX_PAYLOAD];
|
||||
s_cam_rx_payload[0] = (uint8_t)rssi;
|
||||
memcpy(s_cam_rx_payload + 1, cam_uper, (size_t)cam_len);
|
||||
return send_frame(SERIAL_MSG_CAM_RX, s_cam_rx_payload, 1 + cam_len);
|
||||
static uint8_t s_v2x_payload[SERIAL_LINK_MAX_PAYLOAD];
|
||||
|
||||
// Little-endian prefix, layout documented in serial_link.h - keep in lockstep with the app's
|
||||
// SerialFrame.kt.
|
||||
s_v2x_payload[0] = (uint8_t)(btp_dest_port & 0xFF);
|
||||
s_v2x_payload[1] = (uint8_t)((btp_dest_port >> 8) & 0xFF);
|
||||
s_v2x_payload[2] = (uint8_t)rssi;
|
||||
s_v2x_payload[3] = has_geo_area ? 0x01 : 0x00;
|
||||
uint32_t lat = (uint32_t)geo_area_lat_tenmicrodeg;
|
||||
uint32_t lon = (uint32_t)geo_area_lon_tenmicrodeg;
|
||||
s_v2x_payload[4] = (uint8_t)(lat & 0xFF);
|
||||
s_v2x_payload[5] = (uint8_t)((lat >> 8) & 0xFF);
|
||||
s_v2x_payload[6] = (uint8_t)((lat >> 16) & 0xFF);
|
||||
s_v2x_payload[7] = (uint8_t)((lat >> 24) & 0xFF);
|
||||
s_v2x_payload[8] = (uint8_t)(lon & 0xFF);
|
||||
s_v2x_payload[9] = (uint8_t)((lon >> 8) & 0xFF);
|
||||
s_v2x_payload[10] = (uint8_t)((lon >> 16) & 0xFF);
|
||||
s_v2x_payload[11] = (uint8_t)((lon >> 24) & 0xFF);
|
||||
s_v2x_payload[12] = (uint8_t)(geo_area_distance_a_m & 0xFF);
|
||||
s_v2x_payload[13] = (uint8_t)((geo_area_distance_a_m >> 8) & 0xFF);
|
||||
|
||||
if (uper_len > 0) memcpy(s_v2x_payload + SERIAL_V2X_RX_PREFIX_LEN, uper, (size_t)uper_len);
|
||||
return send_frame(SERIAL_MSG_V2X_RX, s_v2x_payload, SERIAL_V2X_RX_PREFIX_LEN + uper_len);
|
||||
}
|
||||
|
||||
bool serial_link_send_status(uint8_t status)
|
||||
|
||||
Reference in New Issue
Block a user