Drive the bench CAM beacon round a street loop in St. Georg
The bench transmitter sent a parked car: one fixed position, speed 0, no heading, a CAM every second. It now simulates a car driving a loop through six waypoints around Berliner Tor on the real streets, which makes it a moving target for the app's map and the use case detection without taking a car out. The route is generated, not hand-traced. tools/make_route.py asks the OSRM demo server for a driving route through the waypoints and back to the first, thins the 371 street points to 103 (none more than 1.5 m off the line), and writes main/route_points.h. It also saves OSRM's answer (--offline rebuilds from it) and a map page to check the route before flashing. Each waypoint is sent with the direction towards the next one: without it, points on divided roads such as Beim Strohhause snapped to the opposite carriageway and the loop came out at 8.4 km of U-turns. With it the loop is 5.2 km, still including two turn-round detours that OSRM needs to reach the waypoints legally (Borgfelder Strasse / Anckelmannsplatz, and Nagelsweg / Norderstrasse / Repsoldstrasse). Route data (c) OpenStreetMap contributors, ODbL. main/route.c moves the car along the points. It cruises at 50 km/h and limits each bend to the speed that keeps sideways acceleration at 2 m/s^2, so a junction turn is taken at about 15 km/h and a gentle curve barely slows it; braking (2 m/s^2) and acceleration (1.5 m/s^2) are planned across as many points as a bend needs. A simulated lap on the host is 5.16 km in 7.7 min, averaging 40 km/h. CAMs now follow the EN 302 637-2 generation rules instead of a fixed 1 Hz: checked every 100 ms, sent on a heading change over 4 degrees, a move over 4 m, a speed change over 0.5 m/s, or after 1 s - about 3 Hz at 50 km/h. generationDeltaTime is milliseconds since boot. The GeoNetworking source position vector now carries the same speed and heading as the CAM instead of zeros. NOTES.md gains build and flash steps (including reading a board's app descriptor first, since both firmwares name their image obu_firmware.bin) and a section on the simulated drive. The pointer to docs/04-transmit-setup.md is corrected: that file is not in the repo. Flashed to the COM8 board and checked on its console: it starts driving on power-up and sends CAMs with changing position, speed and heading. Not yet received over the air.
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_timer.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
@@ -14,13 +18,17 @@
|
||||
#include "geonet.h"
|
||||
#include "dot11p.h"
|
||||
#include "tx_custom.h"
|
||||
#include "route.h"
|
||||
#include "route_points.h"
|
||||
|
||||
static const char *TAG = "obu-tx";
|
||||
|
||||
// CAM beacon: transmit a Cooperative Awareness Message every TX_INTERVAL_MS,
|
||||
// unconditionally (no hazard-light gating - CAM is a continuous beacon, unlike
|
||||
// the event-triggered DENM). Matches the working Rust reference
|
||||
// (esp32-c_its-companion, feat/tx-cam), which beacons CAM on 5900 MHz.
|
||||
// CAM beacon for a simulated car driving round a block in Hamburg (see route.c). The CAM
|
||||
// generation rules follow ETSI EN 302 637-2 clause 6.1.3: every CHECK_INTERVAL_MS the car's state
|
||||
// is compared with the last CAM sent, and a new CAM goes out when the heading changed by more than
|
||||
// 4 degrees, the position by more than 4 m, the speed by more than 0.5 m/s, or 1 s has passed.
|
||||
// There is no hazard-light gating - CAM is a continuous beacon, unlike the event-triggered DENM.
|
||||
// Transmits on 5900 MHz like the working Rust reference (esp32-c_its-companion, feat/tx-cam).
|
||||
|
||||
// ISOLATION TEST for whether tx_custom.c is the blocker.
|
||||
// 1 = transmit via the STANDARD, well-tested esp_wifi_80211_tx() using a
|
||||
@@ -55,15 +63,19 @@ static const char *TAG = "obu-tx";
|
||||
#define VEHICLE_LENGTH_DM 40 // VehicleLengthValue, 10cm steps (4.0 m)
|
||||
#define VEHICLE_WIDTH_DM 18 // VehicleWidth, 10cm steps (1.8 m)
|
||||
#define BTP_PORT_CAM 2001 // BTP-B destination port for CAM (ETSI TS 103 248)
|
||||
#define TX_INTERVAL_MS 1000 // CAM beacon period (1 Hz; ITS allows 1-10 Hz)
|
||||
#define CHECK_INTERVAL_MS 100 // T_CheckCamGen: how often the generation rules are evaluated
|
||||
#define CAM_MAX_INTERVAL_MS 1000 // T_GenCamMax: a CAM goes out at least this often
|
||||
#define CAM_HEADING_DDEG 40 // > 4 degrees heading change triggers a CAM
|
||||
#define CAM_POSITION_M 4.0 // > 4 m position change triggers a CAM
|
||||
#define CAM_SPEED_CM_S 50 // > 0.5 m/s speed change triggers a CAM
|
||||
|
||||
// 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 (decimal_degrees * 10,000,000). Replace with real
|
||||
// GNSS output once you have a fix source; until then this beats 0/0
|
||||
// ("Null Island"), which is an obvious placeholder-tell on any map.
|
||||
#define BENCH_LATITUDE_TENMICRODEG 535546667
|
||||
#define BENCH_LONGITUDE_TENMICRODEG 100223889
|
||||
// ---- Simulated drive ----
|
||||
// route_points (main/route_points.h) is the street geometry of a driving loop through six waypoints
|
||||
// in St. Georg, generated by tools/make_route.py from OpenStreetMap via OSRM. To change the route,
|
||||
// edit WAYPOINTS in that script and rerun it. No GNSS is wired in; replace with real fixes once
|
||||
// there is one.
|
||||
#define CRUISE_MPS (50.0 / 3.6) // 50 km/h, the urban limit
|
||||
#define MIN_CORNER_MPS (10.0 / 3.6) // slowest the car goes, for hairpins and U-turns
|
||||
|
||||
// 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
|
||||
@@ -78,33 +90,28 @@ static const uint8_t pseudonym_mac[6] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
|
||||
extern void phy_11p_set(int enable, int unused);
|
||||
extern void phy_change_channel(int freq_mhz, int bw_mode, int sec_chan_offset, int unused);
|
||||
|
||||
static void send_cam(void)
|
||||
static void send_cam(const route_state_t *car, uint16_t gen_delta)
|
||||
{
|
||||
// GenerationDeltaTime is TimestampIts mod 65536 (ms). No RTC/GNSS time here,
|
||||
// so use a free-running ms counter that advances one beacon-interval per
|
||||
// send. It wraps at 65536, which is exactly the field's defined behaviour.
|
||||
static uint16_t gen_delta = 0;
|
||||
|
||||
uint8_t frame[300];
|
||||
cam_fields_t fields = {
|
||||
.station_id = STATION_ID,
|
||||
.station_type = STATION_TYPE,
|
||||
.generation_delta_time = gen_delta,
|
||||
.latitude_tenmicrodeg = BENCH_LATITUDE_TENMICRODEG,
|
||||
.longitude_tenmicrodeg = BENCH_LONGITUDE_TENMICRODEG,
|
||||
.speed_cm_s = 0, // stationary
|
||||
.heading_ddeg = 3601, // HeadingValue unavailable (no heading source)
|
||||
.latitude_tenmicrodeg = car->latitude_tenmicrodeg,
|
||||
.longitude_tenmicrodeg = car->longitude_tenmicrodeg,
|
||||
.speed_cm_s = car->speed_cm_s,
|
||||
.heading_ddeg = car->heading_ddeg,
|
||||
.vehicle_length_dm = VEHICLE_LENGTH_DM,
|
||||
.vehicle_width_dm = VEHICLE_WIDTH_DM,
|
||||
};
|
||||
gen_delta += TX_INTERVAL_MS;
|
||||
|
||||
uint8_t cam_payload[96];
|
||||
int cam_len = cam_encode(&fields, cam_payload, sizeof(cam_payload));
|
||||
|
||||
uint8_t gn_payload[160];
|
||||
int gn_len = geonet_wrap_shb(cam_payload, cam_len, pseudonym_mac, STATION_TYPE,
|
||||
BENCH_LATITUDE_TENMICRODEG, BENCH_LONGITUDE_TENMICRODEG,
|
||||
car->latitude_tenmicrodeg, car->longitude_tenmicrodeg,
|
||||
car->speed_cm_s, car->heading_ddeg,
|
||||
BTP_PORT_CAM, gn_payload, sizeof(gn_payload));
|
||||
|
||||
// qos=false for the standard-TX path (esp_wifi_80211_tx accepts only non-QoS
|
||||
@@ -127,7 +134,10 @@ static void send_cam(void)
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_wifi_80211_tx (standard) failed: %d", err);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "CAM sent via STANDARD tx (%d bytes) @ %d MHz genDeltaT=%u", frame_len, TX_FREQ_MHZ, gen_delta);
|
||||
ESP_LOGI(TAG, "CAM sent (%d bytes) @ %d MHz genDeltaT=%u pos=%.7f,%.7f %.1f km/h heading %.1f pt%d",
|
||||
frame_len, TX_FREQ_MHZ, gen_delta,
|
||||
car->latitude_tenmicrodeg / 1e7, car->longitude_tenmicrodeg / 1e7,
|
||||
car->speed_cm_s * 0.036, car->heading_ddeg / 10.0, car->segment + 1);
|
||||
}
|
||||
#else
|
||||
// tx_custom path: submits to the driver's internal HMAC TX path,
|
||||
@@ -151,12 +161,49 @@ static void send_cam(void)
|
||||
}
|
||||
}
|
||||
|
||||
static bool cam_due(const route_state_t *car, const route_state_t *last, int64_t since_last_ms)
|
||||
{
|
||||
if (since_last_ms >= CAM_MAX_INTERVAL_MS) {
|
||||
return true;
|
||||
}
|
||||
int dh = abs((int)car->heading_ddeg - (int)last->heading_ddeg);
|
||||
if (dh > 1800) {
|
||||
dh = 3600 - dh;
|
||||
}
|
||||
if (dh > CAM_HEADING_DDEG) {
|
||||
return true;
|
||||
}
|
||||
if (abs((int)car->speed_cm_s - (int)last->speed_cm_s) > CAM_SPEED_CM_S) {
|
||||
return true;
|
||||
}
|
||||
// Flat-earth distance is plenty for a 4 m threshold.
|
||||
double north_m = (car->latitude_tenmicrodeg - last->latitude_tenmicrodeg) * 0.0111194930;
|
||||
double east_m = (car->longitude_tenmicrodeg - last->longitude_tenmicrodeg) * 0.0111194930
|
||||
* cos(car->latitude_tenmicrodeg / 1e7 * M_PI / 180.0);
|
||||
return north_m * north_m + east_m * east_m > CAM_POSITION_M * CAM_POSITION_M;
|
||||
}
|
||||
|
||||
static void tx_task(void *arg)
|
||||
{
|
||||
route_state_t car;
|
||||
route_state_t last_sent;
|
||||
int64_t last_sent_ms = 0;
|
||||
bool sent_any = false;
|
||||
TickType_t wake = xTaskGetTickCount();
|
||||
|
||||
route_step(0.0, &car);
|
||||
while (1) {
|
||||
// CAM is a continuous beacon - send every interval, unconditionally.
|
||||
send_cam();
|
||||
vTaskDelay(pdMS_TO_TICKS(TX_INTERVAL_MS));
|
||||
int64_t now_ms = esp_timer_get_time() / 1000;
|
||||
if (!sent_any || cam_due(&car, &last_sent, now_ms - last_sent_ms)) {
|
||||
// GenerationDeltaTime is TimestampIts mod 65536 (ms). No real clock here, so use
|
||||
// milliseconds since boot, which advances at the right rate.
|
||||
send_cam(&car, (uint16_t)now_ms);
|
||||
last_sent = car;
|
||||
last_sent_ms = now_ms;
|
||||
sent_any = true;
|
||||
}
|
||||
vTaskDelayUntil(&wake, pdMS_TO_TICKS(CHECK_INTERVAL_MS));
|
||||
route_step(CHECK_INTERVAL_MS / 1000.0, &car);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,8 +302,13 @@ void app_main(void)
|
||||
phy_change_channel(TX_FREQ_MHZ, 1, 0, 0);
|
||||
ESP_LOGI(TAG, "phy_change_channel returned");
|
||||
|
||||
ESP_LOGW(TAG, "OCB @ %d MHz - CAM beacon armed, transmitting every %d ms",
|
||||
TX_FREQ_MHZ, TX_INTERVAL_MS);
|
||||
if (route_init(route_points, sizeof(route_points) / sizeof(route_points[0]),
|
||||
CRUISE_MPS, MIN_CORNER_MPS) != 0) {
|
||||
ESP_LOGE(TAG, "route_init failed - check route_points");
|
||||
return;
|
||||
}
|
||||
ESP_LOGW(TAG, "OCB @ %d MHz - CAM beacon armed, driving a %d-point street loop",
|
||||
TX_FREQ_MHZ, (int)(sizeof(route_points) / sizeof(route_points[0])));
|
||||
|
||||
xTaskCreate(tx_task, "tx_task", 4096, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user