Send CAMs under the phone's position vector, not bench placeholders
Every field of the GeoNetworking Source Position Vector this firmware sent was a compile-time constant: the bench coordinates, speed 0, heading 0, TST 0, station type passengerCar and one fixed MAC. The CAM inside described a moving cyclist while the GN header around it described a car parked at the bench. SERIAL_MSG_CAM_TX_PV (0x05) puts a 24-byte prefix ahead of the CAM UPER: MAC, station type, PAI, TST, latitude, longitude, speed and heading, all values the phone already has when it builds the CAM and none of which this chip can know. geonet_wrap_shb now takes them as a gn_lpv_t, and tx_radio_task hands the same MAC to dot11p_build_frame, so the 802.11 source address and the GN_ADDR MID stay one address across a pseudonym change. Speed is clamped rather than masked, since an overflowing 15-bit value flips its sign bit and reads as travelling backwards. This reverses the Phase 03 decision that the firmware owns the pseudonym. A pseudonym only protects anyone if the MAC, the GN_ADDR and the CAM's stationID change together, and the phone owns the stationID. The heartbeat gains a capability byte (payload[7], bit0 = CAM_TX_PV), appended so an app reading the first 7 bytes is unaffected. The app sends 0x05 only once it sees that bit, so app and firmware can be updated in either order. CAM_TX (0x01) is still handled and falls back to the bench values, with the station type corrected to cyclist to match the CAM. Verified on air from the COM10 test board, decoded independently by the CiT One's gnHeader: 24 of 24 CAM_TX_PV frames matched the sent position vector field by field, and so did the CAM station ID. The legacy path delivered 23 of 24 frames with no field mismatches. Flashed on the COM3 OBU and its boot log is clean. Also corrects the SERIAL_LINK_MAX_PAYLOAD comment, which still named the 400-byte receive capture buffer as the ceiling on the RX path. That buffer is 800 bytes now, so the serial link is the ceiling, and larger payloads are dropped and counted there.
This commit is contained in:
+76
-20
@@ -21,7 +21,9 @@
|
||||
static const char *TAG = "obu-tx";
|
||||
|
||||
// Phase 03: CAM is no longer built on this chip. The phone fuses its own GNSS+IMU, UPER-encodes
|
||||
// CAM itself, and hands the finished bytes down over serial_link (SERIAL_MSG_CAM_TX) - this
|
||||
// CAM itself, and hands the finished bytes down over serial_link (SERIAL_MSG_CAM_TX_PV, together
|
||||
// with the GeoNetworking position vector to send them under; plain SERIAL_MSG_CAM_TX from an app
|
||||
// that predates it) - this
|
||||
// firmware's job on transmit shrinks to "GeoNetworking/BTP-wrap + 802.11-wrap + key the PA the
|
||||
// instant a CAM arrives." There is no on-chip transmit timer anymore; the phone's send cadence
|
||||
// (1 Hz baseline, faster near intersections/events - all decided app-side) IS the air cadence.
|
||||
@@ -41,26 +43,25 @@ static const char *TAG = "obu-tx";
|
||||
#define TX_FREQ_MHZ 5900
|
||||
|
||||
// ---- CAM beacon profile (used for the GeoNetworking layer only now - see below) ----
|
||||
#define STATION_TYPE 5 // passengerCar (TS 102 894-2 StationType) - matches gn_addr's ST field
|
||||
#define STATION_TYPE 2 // cyclist (TS 102 894-2 StationType), legacy CAM_TX path only - see legacy_lpv()
|
||||
#define BTP_PORT_CAM 2001 // BTP-B destination port for CAM (ETSI TS 103 248)
|
||||
|
||||
// Bench location, hardcoded since there's no GNSS module wired in yet and the unit is genuinely
|
||||
// stationary here: 53°33'16.8"N 10°01'20.6"E, in 1/10-microdegree units. Used ONLY for the
|
||||
// GeoNetworking Source Long Position Vector now (geonet_wrap_shb's own claimed position) - the
|
||||
// CAM payload's own referencePosition comes from the phone's real GNSS and can legitimately
|
||||
// differ from this bench placeholder until the GN layer is also given a real position source.
|
||||
// TODO: feed this from the phone too (e.g. a lightweight position update piggybacked on
|
||||
// SERIAL_MSG_CAM_TX, or a new small message type) instead of a fixed bench location.
|
||||
// Bench location, 53°33'16.8"N 10°01'20.6"E, in 1/10-microdegree units. Used only by the legacy
|
||||
// SERIAL_MSG_CAM_TX path (see legacy_lpv), which carries no position of its own. A current app
|
||||
// sends SERIAL_MSG_CAM_TX_PV instead, and the GN Source Position Vector then comes from the
|
||||
// phone's real fix, the same one the CAM payload's own referencePosition is built from.
|
||||
#define BENCH_LATITUDE_TENMICRODEG 535546667
|
||||
#define BENCH_LONGITUDE_TENMICRODEG 100223889
|
||||
|
||||
// Single source of truth for the pseudonym/link-layer address: used both as
|
||||
// the 802.11 source MAC (Addr2) and as GN_ADDR's MID field, since the GN
|
||||
// spec defines those as being the same address. Locally-administered bit
|
||||
// set (0x02) per normal MAC convention. Fixed/non-rotating for now - real
|
||||
// stacks rotate this every 5-15 min for privacy. Owned entirely by this firmware (not the
|
||||
// phone) per the Phase 03 design decision - simplest given the phone never needs to know it.
|
||||
static const uint8_t pseudonym_mac[6] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
|
||||
// Link-layer address for the legacy SERIAL_MSG_CAM_TX path only. Locally-administered bit set
|
||||
// (0x02), per normal MAC convention.
|
||||
//
|
||||
// This reverses the Phase 03 decision that the pseudonym is owned entirely by this firmware. That
|
||||
// was simplest while the address never changed, but a pseudonym only protects anyone if the
|
||||
// 802.11 address, the GN_ADDR MID and the CAM's stationID all change together, and the phone owns
|
||||
// the stationID. One identity needs one owner, so with CAM_TX_PV the phone sends the address with
|
||||
// every frame and rotates it, and this constant is only what the legacy path falls back to.
|
||||
static const uint8_t LEGACY_MAC[6] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
|
||||
|
||||
// Undocumented libphy.a calls that push the radio into 802.11p OCB mode on
|
||||
// the 5.9 GHz ITS-G5 band. See docs/04-transmit-setup.md for source + what
|
||||
@@ -77,6 +78,7 @@ extern void phy_change_channel(int freq_mhz, int bw_mode, int sec_chan_offset, i
|
||||
typedef struct {
|
||||
uint8_t data[SERIAL_LINK_MAX_PAYLOAD];
|
||||
int len;
|
||||
gn_lpv_t lpv; // the Source Position Vector this CAM goes out under
|
||||
} cam_tx_item_t;
|
||||
|
||||
static QueueHandle_t s_tx_queue;
|
||||
@@ -87,6 +89,23 @@ static QueueHandle_t s_tx_queue;
|
||||
// tx_radio_task below, off the UART parsing path entirely. xQueueSend with 0 timeout: if the
|
||||
// radio task is somehow behind, drop this CAM rather than stall UART frame parsing - the next
|
||||
// one is only ~1s (or less, at elevated rate) away regardless.
|
||||
// Source Position Vector for the legacy SERIAL_MSG_CAM_TX path, which carries no position of its
|
||||
// own. Everything here describes the bench, not the rider: a fixed point, standing still, at an
|
||||
// unknown time, under a fixed address. That is exactly why the phone now sends CAM_TX_PV. Kept so
|
||||
// an app that predates it still transmits what it always did, except that the station type now
|
||||
// says cyclist to agree with the CAM inside.
|
||||
static void legacy_lpv(gn_lpv_t *lpv)
|
||||
{
|
||||
memcpy(lpv->mac, LEGACY_MAC, sizeof(lpv->mac));
|
||||
lpv->station_type = STATION_TYPE;
|
||||
lpv->pai = false;
|
||||
lpv->tst_ms = 0;
|
||||
lpv->lat_tenmicrodeg = BENCH_LATITUDE_TENMICRODEG;
|
||||
lpv->lon_tenmicrodeg = BENCH_LONGITUDE_TENMICRODEG;
|
||||
lpv->speed_cms = 0;
|
||||
lpv->heading_decideg = 0;
|
||||
}
|
||||
|
||||
static void on_cam_tx_from_phone(const uint8_t *cam_uper, int cam_len)
|
||||
{
|
||||
if (cam_len <= 0 || cam_len > SERIAL_LINK_MAX_PAYLOAD) {
|
||||
@@ -96,6 +115,42 @@ static void on_cam_tx_from_phone(const uint8_t *cam_uper, int cam_len)
|
||||
cam_tx_item_t item;
|
||||
item.len = cam_len;
|
||||
memcpy(item.data, cam_uper, (size_t)cam_len);
|
||||
legacy_lpv(&item.lpv);
|
||||
if (xQueueSend(s_tx_queue, &item, 0) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "tx queue full, dropping CAM from phone");
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t le16(const uint8_t *p)
|
||||
{
|
||||
return (uint16_t)(p[0] | (p[1] << 8));
|
||||
}
|
||||
|
||||
static uint32_t le32(const uint8_t *p)
|
||||
{
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
// SERIAL_MSG_CAM_TX_PV: the phone's CAM plus the position vector to send it under. The prefix
|
||||
// layout is documented at SERIAL_MSG_CAM_TX_PV in serial_link.h. Same speed constraint as
|
||||
// on_cam_tx_from_phone: decode, queue, return.
|
||||
static void on_cam_tx_pv_from_phone(const uint8_t *prefix, const uint8_t *cam_uper, int cam_len)
|
||||
{
|
||||
if (cam_len <= 0 || cam_len > SERIAL_LINK_MAX_PAYLOAD) {
|
||||
ESP_LOGW(TAG, "on_cam_tx_pv_from_phone: bad length %d", cam_len);
|
||||
return;
|
||||
}
|
||||
cam_tx_item_t item;
|
||||
item.len = cam_len;
|
||||
memcpy(item.data, cam_uper, (size_t)cam_len);
|
||||
memcpy(item.lpv.mac, prefix, sizeof(item.lpv.mac));
|
||||
item.lpv.station_type = prefix[6];
|
||||
item.lpv.pai = (prefix[7] & 0x01) != 0;
|
||||
item.lpv.tst_ms = le32(prefix + 8);
|
||||
item.lpv.lat_tenmicrodeg = (int32_t)le32(prefix + 12);
|
||||
item.lpv.lon_tenmicrodeg = (int32_t)le32(prefix + 16);
|
||||
item.lpv.speed_cms = (int16_t)le16(prefix + 20);
|
||||
item.lpv.heading_decideg = le16(prefix + 22);
|
||||
if (xQueueSend(s_tx_queue, &item, 0) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "tx queue full, dropping CAM from phone");
|
||||
}
|
||||
@@ -116,8 +171,7 @@ static void tx_radio_task(void *arg)
|
||||
// singleton, created once in app_main. Both wrap functions bounds-check against the size
|
||||
// passed in and return <= 0 on overflow, so an oversized CAM is rejected, not written past.
|
||||
static uint8_t gn_payload[SERIAL_LINK_MAX_PAYLOAD + 64];
|
||||
int gn_len = geonet_wrap_shb(item.data, item.len, pseudonym_mac, STATION_TYPE,
|
||||
BENCH_LATITUDE_TENMICRODEG, BENCH_LONGITUDE_TENMICRODEG,
|
||||
int gn_len = geonet_wrap_shb(item.data, item.len, &item.lpv,
|
||||
BTP_PORT_CAM, gn_payload, sizeof(gn_payload));
|
||||
if (gn_len <= 0) {
|
||||
ESP_LOGW(TAG, "geonet_wrap_shb failed (cam_len=%d)", item.len);
|
||||
@@ -125,7 +179,9 @@ static void tx_radio_task(void *arg)
|
||||
}
|
||||
|
||||
static uint8_t frame[SERIAL_LINK_MAX_PAYLOAD + 192];
|
||||
int frame_len = dot11p_build_frame(gn_payload, gn_len, pseudonym_mac, frame,
|
||||
// Source address from the same lpv the GN header was built from, so the 802.11 and
|
||||
// GeoNetworking layers always name the same sender, including across a pseudonym change.
|
||||
int frame_len = dot11p_build_frame(gn_payload, gn_len, item.lpv.mac, frame,
|
||||
sizeof(frame), false);
|
||||
if (frame_len <= 0) {
|
||||
ESP_LOGW(TAG, "dot11p_build_frame failed (gn_len=%d)", gn_len);
|
||||
@@ -339,7 +395,7 @@ void app_main(void)
|
||||
|
||||
xTaskCreate(tx_radio_task, "tx_radio", 4096, NULL, 6, NULL);
|
||||
xTaskCreate(rx_forward_task, "rx_forward", 4096, NULL, 5, NULL);
|
||||
serial_link_init(on_cam_tx_from_phone);
|
||||
serial_link_init(on_cam_tx_from_phone, on_cam_tx_pv_from_phone);
|
||||
|
||||
ESP_LOGW(TAG, "OCB @ %d MHz - TX/RX armed, driven by serial_link (no on-chip TX timer)",
|
||||
TX_FREQ_MHZ);
|
||||
|
||||
Reference in New Issue
Block a user