Skip to main content

denm_sender_receiver/
denm_sender_receiver.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2024 Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT)
3
4//! DENM Sender and Receiver Example
5//!
6//! This example mirrors `FlexStack/examples/cam_sender_and_receiver.py` but
7//! uses the Decentralized Environmental Notification (DEN) Service (DENM)
8//! instead of the CA Basic Service (CAM).
9//!
10//! It shows how to use the `rustflexstack` library to build a minimal V2X node
11//! that:
12//!
13//! 1. Initialises a GeoNetworking router and a BTP router.
14//! 2. Connects them to a raw Ethernet link layer.
15//! 3. Uses `DecentralizedEnvironmentalNotificationService` to send UPER-encoded
16//!    DENMs via GeoBroadcast-Circle on BTP port 2002 and receive + decode any
17//!    incoming DENMs on the same port.
18//! 4. Triggers a Road-Hazard Signalling DENM (accident cause code) once the
19//!    stack is initialised, and repeats it for 30 seconds.
20//!
21//! # Running
22//! ```text
23//! # The raw socket requires CAP_NET_RAW or root:
24//! sudo cargo run --example denm_sender_receiver -- <interface>
25//! # e.g.:
26//! sudo cargo run --example denm_sender_receiver -- eth0
27//! ```
28//!
29//! If no interface is given the example falls back to `lo` (loopback), which
30//! is sufficient to test encode/decode on a single machine.
31
32use rustflexstack::btp::router::Router as BTPRouter;
33use rustflexstack::facilities::decentralized_environmental_notification_service::{
34    denm_coder::{AccidentSubCauseCode, CauseCodeChoice},
35    DENRequest, DecentralizedEnvironmentalNotificationService, VehicleData,
36};
37use rustflexstack::facilities::location_service::{GpsFix, LocationService};
38use rustflexstack::geonet::gn_address::{GNAddress, M, MID, ST};
39use rustflexstack::geonet::mib::Mib;
40use rustflexstack::geonet::position_vector::LongPositionVector;
41use rustflexstack::geonet::router::Router as GNRouter;
42use rustflexstack::link_layer::raw_link_layer::RawLinkLayer;
43use std::env;
44use std::sync::mpsc;
45use std::thread;
46use std::time::Duration;
47
48fn main() {
49    // ── Parse optional interface argument ─────────────────────────────────────
50    let iface = env::args().nth(1).unwrap_or_else(|| "lo".to_string());
51
52    println!("=== DENM Sender/Receiver Example (DEN Service) ===");
53    println!("Interface: {}", iface);
54
55    // ── Generate a random locally-administered MAC ────────────────────────────
56    let mac = {
57        use std::time::{SystemTime, UNIX_EPOCH};
58        let seed = SystemTime::now()
59            .duration_since(UNIX_EPOCH)
60            .unwrap()
61            .subsec_nanos();
62        [
63            0x02u8,
64            (seed >> 24) as u8,
65            (seed >> 16) as u8,
66            (seed >> 8) as u8,
67            seed as u8,
68            0xCC,
69        ]
70    };
71    println!(
72        "MAC: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
73        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
74    );
75
76    // ── MIB ───────────────────────────────────────────────────────────────────
77    let mut mib = Mib::new();
78    mib.itsGnLocalGnAddr = GNAddress::new(M::GnMulticast, ST::PassengerCar, MID::new(mac));
79    mib.itsGnBeaconServiceRetransmitTimer = 0;
80
81    // ── Location Service ──────────────────────────────────────────────────────
82    //
83    // A single LocationService publishes GPS fixes.  We subscribe once for the
84    // GN router's position vector updates.
85    let mut loc_svc = LocationService::new();
86    let gn_gps_rx = loc_svc.subscribe();
87
88    // ── Spawn GeoNetworking router ────────────────────────────────────────────
89    let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None);
90
91    // ── Spawn BTP router ──────────────────────────────────────────────────────
92    let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib);
93
94    // ── Wire RawLinkLayer ─────────────────────────────────────────────────────
95    let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::<Vec<u8>>();
96    let raw_ll = RawLinkLayer::new(ll_to_gn_tx, gn_to_ll_rx, &iface, mac);
97    raw_ll.start();
98
99    // ── Wire threads ──────────────────────────────────────────────────────────
100
101    // LL → GN
102    let gn_h1 = gn_handle.clone();
103    thread::spawn(move || {
104        while let Ok(pkt) = ll_to_gn_rx.recv() {
105            gn_h1.send_incoming_packet(pkt);
106        }
107    });
108
109    // GN → BTP
110    let btp_h1 = btp_handle.clone();
111    thread::spawn(move || {
112        while let Ok(ind) = gn_to_btp_rx.recv() {
113            btp_h1.send_gn_data_indication(ind);
114        }
115    });
116
117    // BTP → GN
118    let gn_h2 = gn_handle.clone();
119    thread::spawn(move || {
120        while let Ok(req) = btp_to_gn_rx.recv() {
121            gn_h2.send_gn_data_request(req);
122        }
123    });
124
125    // ── Bridge: LocationService → GN router position vector ───────────────────
126    let gn_h3 = gn_handle.clone();
127    thread::spawn(move || {
128        while let Ok(fix) = gn_gps_rx.recv() {
129            let mut epv = LongPositionVector::decode([0u8; 24]);
130            epv.update_from_gps(
131                fix.latitude,
132                fix.longitude,
133                fix.speed_mps,
134                fix.heading_deg,
135                fix.pai,
136            );
137            gn_h3.update_position_vector(epv);
138        }
139    });
140
141    // ── DEN Service ───────────────────────────────────────────────────────────
142    let station_id = u32::from_be_bytes([mac[2], mac[3], mac[4], mac[5]]);
143    let vehicle_data = VehicleData {
144        station_id,
145        station_type: 5, // passengerCar
146    };
147
148    // DecentralizedEnvironmentalNotificationService::new returns the service
149    // handle plus a Receiver<Denm> on which decoded incoming DENMs arrive.
150    // The reception thread is started immediately (registers BTP port 2002).
151    let (den_svc, denm_rx) =
152        DecentralizedEnvironmentalNotificationService::new(btp_handle.clone(), vehicle_data);
153
154    // ── Decoded DENM printer ──────────────────────────────────────────────────
155    thread::spawn(move || {
156        while let Ok(denm) = denm_rx.recv() {
157            let lat = denm.denm.management.event_position.latitude.0 as f64 / 1e7;
158            let lon = denm.denm.management.event_position.longitude.0 as f64 / 1e7;
159            let seq = denm.denm.management.action_id.sequence_number.0;
160            println!(
161                "[DENM RX] station={:>10}  seq={:>5}  event_lat={:.5}  event_lon={:.5}",
162                denm.header.station_id.0, seq, lat, lon,
163            );
164        }
165    });
166
167    // ── Publish initial GPS fix to initialise the GN router ───────────────────
168    thread::sleep(Duration::from_millis(100));
169
170    // Publish position fixes to keep the GN position vector up to date.
171    let gps_fix = GpsFix {
172        latitude: 41.552,
173        longitude: 2.134,
174        altitude_m: 120.0,
175        speed_mps: 14.0, // ~50 km/h
176        heading_deg: 90.0,
177        pai: true,
178    };
179    loc_svc.publish(gps_fix);
180
181    // ── Trigger DENM: Road Hazard (accident) at current position ──────────────
182    //
183    // Send 1 DENM/s for 30 s, 1 000 m geo-broadcast circle.
184    println!("Triggering road-hazard DENM (accident) for 30 s @ 1 Hz");
185    den_svc.trigger_denm(DENRequest {
186        event_latitude: gps_fix.latitude,
187        event_longitude: gps_fix.longitude,
188        event_altitude_m: gps_fix.altitude_m,
189        cause_code: CauseCodeChoice::accident2(AccidentSubCauseCode(0)),
190        information_quality: 4,
191        event_speed_raw: (gps_fix.speed_mps * 100.0) as u16,
192        event_heading_raw: (gps_fix.heading_deg * 10.0) as u16,
193        denm_interval_ms: 1_000,
194        time_period_ms: 30_000,
195        relevance_radius_m: 1_000,
196    });
197
198    // ── GPS publisher loop ────────────────────────────────────────────────────
199    println!("Publishing GPS fixes @ 1 Hz — Ctrl+C to stop\n");
200    loop {
201        thread::sleep(Duration::from_secs(1));
202        loc_svc.publish(gps_fix);
203    }
204}