vam_sender_receiver/vam_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//! VAM Sender and Receiver Example
5//!
6//! This example mirrors `FlexStack/examples/cam_sender_and_receiver.py` but
7//! uses the VRU Awareness Service (VAM) instead of the CA Basic Service (CAM).
8//!
9//! It shows how to use the `rustflexstack` library to build a minimal V2X node
10//! that:
11//!
12//! 1. Initialises a GeoNetworking router and a BTP router.
13//! 2. Connects them to a raw Ethernet link layer.
14//! 3. Uses `VruAwarenessService` to send UPER-encoded VAMs at up to 10 Hz
15//! and receive + decode incoming VAMs on BTP port 2018.
16//! 4. Uses `LocationService` to fan GPS fixes out to both the GN router
17//! (position vector updates) and the VRU Awareness Service (VAM generation).
18//!
19//! # Running
20//! ```text
21//! # The raw socket requires CAP_NET_RAW or root:
22//! sudo cargo run --example vam_sender_receiver -- <interface>
23//! # e.g.:
24//! sudo cargo run --example vam_sender_receiver -- eth0
25//! ```
26//!
27//! If no interface is given the example falls back to `lo` (loopback), which
28//! is sufficient to test send→receive on a single machine.
29
30use rustflexstack::btp::router::Router as BTPRouter;
31use rustflexstack::facilities::location_service::{GpsFix, LocationService};
32use rustflexstack::facilities::vru_awareness_service::{DeviceData, VruAwarenessService};
33use rustflexstack::geonet::gn_address::{GNAddress, M, MID, ST};
34use rustflexstack::geonet::mib::Mib;
35use rustflexstack::geonet::position_vector::LongPositionVector;
36use rustflexstack::geonet::router::Router as GNRouter;
37use rustflexstack::link_layer::raw_link_layer::RawLinkLayer;
38use std::env;
39use std::sync::mpsc;
40use std::thread;
41use std::time::Duration;
42
43fn main() {
44 // ── Parse optional interface argument ─────────────────────────────────────
45 let iface = env::args().nth(1).unwrap_or_else(|| "lo".to_string());
46
47 println!("=== VAM Sender/Receiver Example (VRU Awareness Service) ===");
48 println!("Interface: {}", iface);
49
50 // ── Generate a random locally-administered MAC ────────────────────────────
51 let mac = {
52 use std::time::{SystemTime, UNIX_EPOCH};
53 let seed = SystemTime::now()
54 .duration_since(UNIX_EPOCH)
55 .unwrap()
56 .subsec_nanos();
57 [
58 0x02u8,
59 (seed >> 24) as u8,
60 (seed >> 16) as u8,
61 (seed >> 8) as u8,
62 seed as u8,
63 0xBB,
64 ]
65 };
66 println!(
67 "MAC: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
68 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
69 );
70
71 // ── MIB ───────────────────────────────────────────────────────────────────
72 let mut mib = Mib::new();
73 mib.itsGnLocalGnAddr = GNAddress::new(M::GnMulticast, ST::Cyclist, MID::new(mac));
74 mib.itsGnBeaconServiceRetransmitTimer = 0;
75
76 // ── Location Service ──────────────────────────────────────────────────────
77 //
78 // A single LocationService publishes GPS fixes to multiple subscribers.
79 // We subscribe twice: once for the GN router's position vector, and once
80 // for the VRU Awareness Service VAM generation.
81 let mut loc_svc = LocationService::new();
82 let gn_gps_rx = loc_svc.subscribe(); // → GN position vector updates
83 let vru_gps_rx = loc_svc.subscribe(); // → VAM transmission
84
85 // ── Spawn GeoNetworking router ────────────────────────────────────────────
86 let (gn_handle, gn_to_ll_rx, gn_to_btp_rx) = GNRouter::spawn(mib, None, None, None);
87
88 // ── Spawn BTP router ──────────────────────────────────────────────────────
89 let (btp_handle, btp_to_gn_rx) = BTPRouter::spawn(mib);
90
91 // ── Wire RawLinkLayer ─────────────────────────────────────────────────────
92 let (ll_to_gn_tx, ll_to_gn_rx) = mpsc::channel::<Vec<u8>>();
93 let raw_ll = RawLinkLayer::new(ll_to_gn_tx, gn_to_ll_rx, &iface, mac);
94 raw_ll.start();
95
96 // ── Wire threads ──────────────────────────────────────────────────────────
97
98 // LL → GN
99 let gn_h1 = gn_handle.clone();
100 thread::spawn(move || {
101 while let Ok(pkt) = ll_to_gn_rx.recv() {
102 gn_h1.send_incoming_packet(pkt);
103 }
104 });
105
106 // GN → BTP
107 let btp_h1 = btp_handle.clone();
108 thread::spawn(move || {
109 while let Ok(ind) = gn_to_btp_rx.recv() {
110 btp_h1.send_gn_data_indication(ind);
111 }
112 });
113
114 // BTP → GN
115 let gn_h2 = gn_handle.clone();
116 thread::spawn(move || {
117 while let Ok(req) = btp_to_gn_rx.recv() {
118 gn_h2.send_gn_data_request(req);
119 }
120 });
121
122 // ── Bridge: LocationService → GN router position vector ───────────────────
123 //
124 // Converts each GpsFix into a LongPositionVector and pushes it into the
125 // GeoNetworking router so the EPV in every outgoing GN header is current.
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 // ── VRU Awareness Service ─────────────────────────────────────────────────
142 //
143 // DeviceData carries static VRU metadata that goes into every VAM.
144 // station_id is derived from the MAC bytes; station_type 2 = cyclist.
145 let station_id = u32::from_be_bytes([mac[2], mac[3], mac[4], mac[5]]);
146 let device_data = DeviceData {
147 station_id,
148 station_type: 2, // cyclist
149 };
150
151 // VruAwarenessService::new returns the service handle plus a
152 // Receiver<Vam> on which decoded incoming VAMs arrive.
153 let (vru_svc, vam_rx) = VruAwarenessService::new(btp_handle.clone(), device_data);
154
155 // start() consumes the service handle and spawns the TX + RX threads.
156 vru_svc.start(vru_gps_rx);
157
158 // ── Decoded VAM printer ───────────────────────────────────────────────────
159 thread::spawn(move || {
160 while let Ok(vam) = vam_rx.recv() {
161 let lat = vam
162 .vam
163 .vam_parameters
164 .basic_container
165 .reference_position
166 .latitude
167 .0 as f64
168 / 1e7;
169 let lon = vam
170 .vam
171 .vam_parameters
172 .basic_container
173 .reference_position
174 .longitude
175 .0 as f64
176 / 1e7;
177 println!(
178 "[VAM RX] station={:>10} lat={:.5} lon={:.5}",
179 vam.header.0.station_id.0, lat, lon,
180 );
181 }
182 });
183
184 // ── GPS publisher (simulates a real GNSS sensor at 1 Hz) ──────────────────
185 //
186 // In a real application this thread would read from gpsd or a serial port.
187 // Wait briefly for the routers to finish initialising.
188 thread::sleep(Duration::from_millis(100));
189 println!("Publishing GPS fixes @ 10 Hz — Ctrl+C to stop\n");
190
191 loop {
192 thread::sleep(Duration::from_millis(100));
193 // 41.552°N 2.134°E — Parc Tecnològic del Vallès
194 loc_svc.publish(GpsFix {
195 latitude: 41.552,
196 longitude: 2.134,
197 altitude_m: 120.0,
198 speed_mps: 1.5,
199 heading_deg: 90.0,
200 pai: true,
201 });
202 }
203}