Skip to main content

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}