Skip to main content

meshcore_rs/
events.rs

1//! Event system for MeshCore communication
2//!
3//! The reader emits events when packets are received from the device.
4//! Users can subscribe to specific event types with optional attribute filtering.
5
6use std::collections::HashMap;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::Arc;
9use tokio::sync::{broadcast, mpsc, RwLock};
10
11use crate::CHANNEL_SECRET_LEN;
12
13/// Event types emitted by MeshCore
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum EventType {
16    // Connection events
17    Connected,
18    Disconnected,
19
20    // Command responses
21    Ok,
22    Error,
23
24    // Contact events
25    Contacts,
26    NewContact,
27    NextContact,
28
29    // Device info events
30    SelfInfo,
31    DeviceInfo,
32    Battery,
33    CurrentTime,
34    PrivateKey,
35    CustomVars,
36    ChannelInfo,
37    StatsCore,
38    StatsRadio,
39    StatsPackets,
40    AutoAddConfig,
41
42    // Messaging events
43    ContactMsgRecv,
44    ChannelMsgRecv,
45    MsgSent,
46    NoMoreMessages,
47    ContactUri,
48
49    // Push notifications
50    Advertisement,
51    PathUpdate,
52    Ack,
53    MessagesWaiting,
54    RawData,
55    LoginSuccess,
56    LoginFailed,
57
58    // Binary protocol events
59    StatusResponse,
60    TelemetryResponse,
61    MmaResponse,
62    AclResponse,
63    NeighboursResponse,
64    BinaryResponse,
65    PathDiscoveryResponse,
66
67    // Trace and logging
68    TraceData,
69    LogData,
70
71    // Signing
72    SignStart,
73    Signature,
74    Disabled,
75
76    // Control
77    ControlData,
78    DiscoverResponse,
79    AdvertResponse,
80
81    // Unknown
82    Unknown,
83}
84
85/// Payload data for events
86#[derive(Debug, Clone)]
87pub enum EventPayload {
88    /// No payload
89    None,
90    /// String payload (error messages, URIs, etc.)
91    String(String),
92    /// Binary payload
93    Bytes(Vec<u8>),
94    /// Contact list
95    Contacts(Vec<Contact>),
96    /// Single contact
97    Contact(Contact),
98    /// Self-info
99    SelfInfo(SelfInfo),
100    /// Device info
101    DeviceInfo(DeviceInfoData),
102    /// Battery info
103    Battery(BatteryInfo),
104    /// Current time (Unix timestamp)
105    Time(u32),
106    /// Contact message received (direct message from a contact)
107    ContactMessage(ContactMessage),
108    /// Channel message received (message on a group channel)
109    ChannelMessage(ChannelMessage),
110    /// Message sent acknowledgement
111    MsgSent(MsgSentInfo),
112    /// Status response
113    Status(StatusData),
114    /// Channel info
115    ChannelInfo(ChannelInfoData),
116    /// Custom variables
117    CustomVars(HashMap<String, String>),
118    /// Private key (64 bytes)
119    PrivateKey([u8; 64]),
120    /// Signature data
121    Signature(Vec<u8>),
122    /// Sign start info
123    SignStart { max_length: u32 },
124    /// Advertisement
125    Advertisement(AdvertisementData),
126    /// Path update
127    PathUpdate(PathUpdateData),
128    /// ACK
129    Ack { tag: [u8; 4] },
130    /// Trace data
131    TraceData(TraceInfo),
132    /// Telemetry response (raw LPP data)
133    Telemetry(Vec<u8>),
134    /// MMA response
135    Mma(Vec<MmaEntry>),
136    /// ACL response
137    Acl(Vec<AclEntry>),
138    /// Neighbours response
139    Neighbours(NeighboursData),
140    /// Binary response
141    BinaryResponse { tag: [u8; 4], data: Vec<u8> },
142    /// Discover response
143    DiscoverResponse(Vec<DiscoverEntry>),
144    /// Advert response
145    AdvertResponse(AdvertResponseData),
146    /// Stats data
147    Stats(StatsData),
148    /// AutoAdd config
149    AutoAddConfig { flags: u8 },
150    /// RF log data
151    LogData(LogData),
152}
153
154/// Contact information
155#[derive(Debug, Clone)]
156pub struct Contact {
157    /// 32-byte public key
158    pub public_key: [u8; 32],
159    /// Contact type
160    pub contact_type: u8,
161    /// Contact flags
162    pub flags: u8,
163    /// Path length (-1 = flood)
164    pub path_len: i8,
165    /// Output path (up to 64 bytes)
166    pub out_path: Vec<u8>,
167    /// Advertised name
168    pub adv_name: String,
169    /// Last advertisement timestamp
170    pub last_advert: u32,
171    /// Latitude in microdegrees
172    pub adv_lat: i32,
173    /// Longitude in microdegrees
174    pub adv_lon: i32,
175    /// Last modification timestamp
176    pub last_modification_timestamp: u32,
177}
178
179impl Contact {
180    /// Get the 6-byte public key prefix
181    pub fn prefix(&self) -> [u8; 6] {
182        let mut prefix = [0u8; 6];
183        prefix.copy_from_slice(&self.public_key[..6]);
184        prefix
185    }
186
187    /// Get the public key as a hex string
188    pub fn public_key_hex(&self) -> String {
189        crate::parsing::hex_encode(&self.public_key)
190    }
191
192    /// Get the prefix as a hex string
193    pub fn prefix_hex(&self) -> String {
194        crate::parsing::hex_encode(&self.prefix())
195    }
196
197    /// Get latitude as decimal degrees
198    pub fn latitude(&self) -> f64 {
199        self.adv_lat as f64 / 1_000_000.0
200    }
201
202    /// Get longitude as decimal degrees
203    pub fn longitude(&self) -> f64 {
204        self.adv_lon as f64 / 1_000_000.0
205    }
206}
207
208/// Device self-info
209#[derive(Debug, Clone, Default)]
210pub struct SelfInfo {
211    /// Advertisement type
212    pub adv_type: u8,
213    /// TX power
214    pub tx_power: u8,
215    /// Maximum TX power
216    pub max_tx_power: u8,
217    /// 32-byte public key
218    pub public_key: [u8; 32],
219    /// Latitude in microdegrees
220    pub adv_lat: i32,
221    /// Longitude in microdegrees
222    pub adv_lon: i32,
223    /// Multi ack setting
224    pub multi_acks: u8,
225    /// Advertisement location policy
226    pub adv_loc_policy: u8,
227    /// Base telemetry mode (bits 0-1)
228    pub telemetry_mode_base: u8,
229    /// Location telemetry mode (bits 2-3)
230    pub telemetry_mode_loc: u8,
231    /// Environment telemetry mode (bits 4-5)
232    pub telemetry_mode_env: u8,
233    /// Manually add contact setting
234    pub manual_add_contacts: bool,
235    /// Radio frequency in mHz
236    pub radio_freq: u32,
237    /// Radio bandwidth in mHz
238    pub radio_bw: u32,
239    /// Spreading factor
240    pub sf: u8,
241    /// Coding rate
242    pub cr: u8,
243    /// Device name
244    pub name: String,
245}
246
247/// Device info/capabilities
248#[derive(Debug, Clone, Default)]
249pub struct DeviceInfoData {
250    /// Firmware version code
251    pub fw_version_code: u8,
252    /// Maximum contacts (multiplied by 2 from raw value, v3+)
253    pub max_contacts: Option<u8>,
254    /// Maximum group channels (v3+)
255    pub max_channels: Option<u8>,
256    /// BLE PIN code (v3+)
257    pub ble_pin: Option<u32>,
258    /// Firmware build date string (e.g., "Feb 15 2025", v3+)
259    pub fw_build: Option<String>,
260    /// Device model/manufacturer name (v3+)
261    pub model: Option<String>,
262    /// Firmware version string (e.g., "1.2.3", v3+)
263    pub version: Option<String>,
264    /// Repeat/relay mode enabled (v9+)
265    pub repeat: Option<bool>,
266}
267
268/// Battery and storage information
269#[derive(Debug, Clone)]
270pub struct BatteryInfo {
271    /// Battery voltage in millivolts
272    pub battery_mv: u16,
273    /// Used storage in KB (if available)
274    pub used_kb: Option<u32>,
275    /// Total storage in KB (if available)
276    pub total_kb: Option<u32>,
277}
278
279impl BatteryInfo {
280    /// Minimum battery voltage in millivolts (0% charge)
281    const MIN_MV: u16 = 3000;
282    /// Maximum battery voltage in millivolts (100% charge)
283    const MAX_MV: u16 = 3930;
284
285    /// Get battery voltage in volts
286    pub fn voltage(&self) -> f32 {
287        self.battery_mv as f32 / 1000.0
288    }
289
290    /// Get estimated battery percentage (0-100) based on voltage.
291    /// Uses linear interpolation: 3000mV = 0%, 3930mV = 100%
292    pub fn percentage(&self) -> u8 {
293        if self.battery_mv <= Self::MIN_MV {
294            0
295        } else if self.battery_mv >= Self::MAX_MV {
296            100
297        } else {
298            ((self.battery_mv - Self::MIN_MV) as u32 * 100 / (Self::MAX_MV - Self::MIN_MV) as u32)
299                as u8
300        }
301    }
302}
303
304/// Contact message - a direct message from a contact (identified by sender public key prefix)
305#[derive(Debug, Clone)]
306pub struct ContactMessage {
307    /// Sender public key prefix (6 bytes)
308    pub sender_prefix: [u8; 6],
309    /// Path length
310    pub path_len: u8,
311    /// Text type (0 = plain, 2 = signed)
312    pub txt_type: u8,
313    /// Sender timestamp
314    pub sender_timestamp: u32,
315    /// Message text
316    pub text: String,
317    /// SNR (only in v3, divided by 4)
318    pub snr: Option<f32>,
319    /// Signature (if txt_type == 2)
320    pub signature: Option<[u8; 4]>,
321}
322
323impl ContactMessage {
324    /// Generate a "unique-ish" message ID for this message
325    pub fn message_id(&self) -> u64 {
326        let mut bytes = [0u8; 8];
327        // Use the first 4 bytes of sender_prefix
328        bytes[0..4].copy_from_slice(&self.sender_prefix[0..4]);
329        // XOR the timestamp into the remaining 4 bytes for uniqueness
330        bytes[4..8].copy_from_slice(&self.sender_timestamp.to_be_bytes());
331        u64::from_be_bytes(bytes)
332    }
333
334    /// Get the sender prefix as a hex string
335    pub fn sender_prefix_hex(&self) -> String {
336        crate::parsing::hex_encode(&self.sender_prefix)
337    }
338}
339
340/// Channel message - a message received on a group channel (identified by channel index)
341#[derive(Debug, Clone)]
342pub struct ChannelMessage {
343    /// Channel index
344    pub channel_idx: u8,
345    /// Path length
346    pub path_len: u8,
347    /// Text type (0 = plain)
348    pub txt_type: u8,
349    /// Sender timestamp
350    pub sender_timestamp: u32,
351    /// Message text
352    pub text: String,
353    /// SNR (only in v3, divided by 4)
354    pub snr: Option<f32>,
355}
356
357impl ChannelMessage {
358    /// Generate a "unique-ish" message ID for this message
359    pub fn message_id(&self) -> u64 {
360        let mut bytes = [0u8; 8];
361        // Use the channel index in the first byte
362        bytes[0] = self.channel_idx;
363        // Use the timestamp for uniqueness
364        bytes[4..8].copy_from_slice(&self.sender_timestamp.to_be_bytes());
365        u64::from_be_bytes(bytes)
366    }
367}
368
369/// Message sent acknowledgement
370#[derive(Debug, Clone)]
371pub struct MsgSentInfo {
372    /// Message type
373    pub message_type: u8,
374    /// Expected ACK tag
375    pub expected_ack: [u8; 4],
376    /// Suggested timeout in milliseconds
377    pub suggested_timeout: u32,
378}
379
380/// Status data from a device
381#[derive(Debug, Clone)]
382pub struct StatusData {
383    /// Battery voltage in millivolts
384    pub battery_mv: u16,
385    /// TX queue length
386    pub tx_queue_len: u16,
387    /// Noise floor (dBm)
388    pub noise_floor: i16,
389    /// Last RSSI (dBm)
390    pub last_rssi: i16,
391    /// Number of packets received
392    pub nb_recv: u32,
393    /// Number of packets sent
394    pub nb_sent: u32,
395    /// Total airtime (ms)
396    pub airtime: u32,
397    /// Uptime (seconds)
398    pub uptime: u32,
399    /// Flood packets sent
400    pub flood_sent: u32,
401    /// Direct packets sent
402    pub direct_sent: u32,
403    /// SNR (divided by 4)
404    pub snr: f32,
405    /// Duplicate packet count
406    pub dup_count: u32,
407    /// RX airtime (ms)
408    pub rx_airtime: u32,
409    /// Sender public key prefix
410    pub sender_prefix: [u8; 6],
411}
412
413/// Channel info
414#[derive(Debug, Clone)]
415pub struct ChannelInfoData {
416    /// Channel index
417    pub channel_idx: u8,
418    /// Channel name
419    pub name: String,
420    /// Channel secret (CHANNEL_SECRET_LEN bytes)
421    pub secret: [u8; CHANNEL_SECRET_LEN],
422}
423
424/// Advertisement data
425#[derive(Debug, Clone)]
426pub struct AdvertisementData {
427    /// Advertiser public key prefix
428    pub prefix: [u8; 6],
429    /// Advertisement name
430    pub name: String,
431    /// Latitude in microdegrees
432    pub lat: i32,
433    /// Longitude in microdegrees
434    pub lon: i32,
435}
436
437/// Path update data
438#[derive(Debug, Clone)]
439pub struct PathUpdateData {
440    /// Node public key prefix
441    pub prefix: [u8; 6],
442    /// New path length
443    pub path_len: i8,
444    /// New path
445    pub path: Vec<u8>,
446}
447
448/// Trace info
449#[derive(Debug, Clone)]
450pub struct TraceInfo {
451    /// Hops with SNR values
452    pub hops: Vec<TraceHop>,
453}
454
455/// Single hop in a trace
456#[derive(Debug, Clone)]
457pub struct TraceHop {
458    /// Node prefix
459    pub prefix: [u8; 6],
460    /// SNR at this hop
461    pub snr: f32,
462}
463
464/// Min/Max/Avg entry
465#[derive(Debug, Clone)]
466pub struct MmaEntry {
467    /// Channel
468    pub channel: u8,
469    /// Type
470    pub entry_type: u8,
471    /// Minimum value
472    pub min: f32,
473    /// Maximum value
474    pub max: f32,
475    /// Average value
476    pub avg: f32,
477}
478
479/// ACL entry
480#[derive(Debug, Clone)]
481pub struct AclEntry {
482    /// Public key prefix (6 bytes)
483    pub prefix: [u8; 6],
484    /// Permissions
485    pub permissions: u8,
486}
487
488/// Neighbours response data
489#[derive(Debug, Clone)]
490pub struct NeighboursData {
491    /// Total neighbours available
492    pub total: u16,
493    /// Neighbours in this response
494    pub neighbours: Vec<Neighbour>,
495}
496
497/// Single neighbour entry
498#[derive(Debug, Clone)]
499pub struct Neighbour {
500    /// Public key (variable length)
501    pub pubkey: Vec<u8>,
502    /// Seconds since last seen
503    pub secs_ago: i32,
504    /// SNR (divided by 4)
505    pub snr: f32,
506}
507
508/// Discover entry
509#[derive(Debug, Clone)]
510pub struct DiscoverEntry {
511    /// Node public key
512    pub pubkey: Vec<u8>,
513    /// Node name
514    pub name: String,
515}
516
517/// Advertisement response data
518#[derive(Debug, Clone)]
519pub struct AdvertResponseData {
520    /// Tag
521    pub tag: [u8; 4],
522    /// Public key
523    pub pubkey: [u8; 32],
524    /// Advertisement type
525    pub adv_type: u8,
526    /// Node name
527    pub node_name: String,
528    /// Timestamp
529    pub timestamp: u32,
530    /// Flags
531    pub flags: u8,
532    /// Latitude (optional)
533    pub lat: Option<i32>,
534    /// Longitude (optional)
535    pub lon: Option<i32>,
536    /// Node description (optional)
537    pub node_desc: Option<String>,
538}
539
540/// Stats data
541#[derive(Debug, Clone)]
542pub struct StatsData {
543    /// Stats category
544    pub category: StatsCategory,
545    /// Raw stats bytes
546    pub raw: Vec<u8>,
547}
548
549/// Stats category
550#[derive(Debug, Clone, Copy, PartialEq, Eq)]
551pub enum StatsCategory {
552    Core,
553    Radio,
554    Packets,
555}
556
557/// RF log data from the device
558#[derive(Debug, Clone)]
559pub struct LogData {
560    /// Signal-to-noise ratio (signed byte / 4.0)
561    pub snr: f32,
562    /// Received signal strength indicator (dBm)
563    pub rssi: i16,
564    /// Raw RF payload
565    pub payload: Vec<u8>,
566}
567
568/// An event emitted by the reader
569#[derive(Debug, Clone)]
570pub struct MeshCoreEvent {
571    /// Event type
572    pub event_type: EventType,
573    /// Event payload
574    pub payload: EventPayload,
575    /// Filterable attributes
576    pub attributes: HashMap<String, String>,
577}
578
579impl MeshCoreEvent {
580    /// Create a new event
581    pub fn new(event_type: EventType, payload: EventPayload) -> Self {
582        Self {
583            event_type,
584            payload,
585            attributes: HashMap::new(),
586        }
587    }
588
589    /// Add an attribute to the event
590    pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
591        self.attributes.insert(key.into(), value.into());
592        self
593    }
594
595    /// Create an OK event
596    pub fn ok() -> Self {
597        Self::new(EventType::Ok, EventPayload::None)
598    }
599
600    /// Create an error event
601    pub fn error(msg: impl Into<String>) -> Self {
602        Self::new(EventType::Error, EventPayload::String(msg.into()))
603    }
604
605    /// Check if this event matches the given filters
606    pub fn matches_filters(&self, filters: &HashMap<String, String>) -> bool {
607        filters
608            .iter()
609            .all(|(k, v)| self.attributes.get(k) == Some(v))
610    }
611}
612
613/// Subscription handle returned when subscribing to events
614#[derive(Debug)]
615pub struct Subscription {
616    id: u64,
617    #[allow(dead_code)]
618    event_type: EventType,
619    unsubscribe_tx: mpsc::Sender<u64>,
620}
621
622impl Subscription {
623    /// Unsubscribe from events
624    pub async fn unsubscribe(self) {
625        let _ = self.unsubscribe_tx.send(self.id).await;
626    }
627}
628
629/// Callback type for event subscriptions
630pub type EventCallback = Box<dyn Fn(MeshCoreEvent) + Send + Sync>;
631
632struct SubscriptionEntry {
633    id: u64,
634    event_type: EventType,
635    filters: HashMap<String, String>,
636    callback: EventCallback,
637}
638
639/// Event dispatcher for managing subscriptions and event distribution
640pub struct EventDispatcher {
641    subscriptions: Arc<RwLock<Vec<SubscriptionEntry>>>,
642    next_id: AtomicU64,
643    broadcast_tx: broadcast::Sender<MeshCoreEvent>,
644    unsubscribe_tx: mpsc::Sender<u64>,
645    unsubscribe_rx: Arc<RwLock<mpsc::Receiver<u64>>>,
646}
647
648impl EventDispatcher {
649    /// Create a new event dispatcher
650    pub fn new() -> Self {
651        let (broadcast_tx, _) = broadcast::channel(256);
652        let (unsubscribe_tx, unsubscribe_rx) = mpsc::channel(64);
653
654        Self {
655            subscriptions: Arc::new(RwLock::new(Vec::new())),
656            next_id: AtomicU64::new(1),
657            broadcast_tx,
658            unsubscribe_tx,
659            unsubscribe_rx: Arc::new(RwLock::new(unsubscribe_rx)),
660        }
661    }
662
663    /// Subscribe to events of a specific type
664    pub async fn subscribe<F>(
665        &self,
666        event_type: EventType,
667        filters: HashMap<String, String>,
668        callback: F,
669    ) -> Subscription
670    where
671        F: Fn(MeshCoreEvent) + Send + Sync + 'static,
672    {
673        let id = self.next_id.fetch_add(1, Ordering::SeqCst);
674
675        let entry = SubscriptionEntry {
676            id,
677            event_type,
678            filters,
679            callback: Box::new(callback),
680        };
681
682        self.subscriptions.write().await.push(entry);
683
684        Subscription {
685            id,
686            event_type,
687            unsubscribe_tx: self.unsubscribe_tx.clone(),
688        }
689    }
690
691    /// Emit an event to all matching subscribers
692    pub async fn emit(&self, event: MeshCoreEvent) {
693        // Process any pending unsubscription events
694        {
695            let mut rx = self.unsubscribe_rx.write().await;
696            while let Ok(id) = rx.try_recv() {
697                self.subscriptions.write().await.retain(|s| s.id != id);
698            }
699        }
700
701        // Notify subscribers
702        let subs = self.subscriptions.read().await;
703        for sub in subs.iter() {
704            if sub.event_type == event.event_type && event.matches_filters(&sub.filters) {
705                (sub.callback)(event.clone());
706            }
707        }
708
709        // Also broadcast for wait_for_event
710        let _ = self.broadcast_tx.send(event);
711    }
712
713    /// Wait for a specific event type with optional filters
714    pub async fn wait_for_event(
715        &self,
716        event_type: Option<EventType>,
717        filters: HashMap<String, String>,
718        timeout: std::time::Duration,
719    ) -> Option<MeshCoreEvent> {
720        let mut rx = self.broadcast_tx.subscribe();
721
722        tokio::select! {
723            _ = tokio::time::sleep(timeout) => None,
724            result = async {
725                loop {
726                    match rx.recv().await {
727                        Ok(event) => {
728                            match event_type {
729                                None => {
730                                    if event.matches_filters(&filters) {
731                                        return Some(event);
732                                    }
733                                }
734                                Some(event_type_filter) => {
735                                    if event.event_type == event_type_filter && event.matches_filters(&filters) {
736                                        return Some(event);
737                                    }
738                                }
739                            }
740                        }
741                        Err(_) => return None,
742                    }
743                }
744            } => result,
745        }
746    }
747
748    /// Get a broadcast receiver for events
749    pub fn receiver(&self) -> broadcast::Receiver<MeshCoreEvent> {
750        self.broadcast_tx.subscribe()
751    }
752}
753
754impl Default for EventDispatcher {
755    fn default() -> Self {
756        Self::new()
757    }
758}
759
760#[cfg(test)]
761mod tests {
762    use super::*;
763    use std::sync::atomic::{AtomicUsize, Ordering};
764    use std::time::Duration;
765
766    #[test]
767    fn test_event_new() {
768        let event = MeshCoreEvent::new(EventType::Ok, EventPayload::None);
769        assert_eq!(event.event_type, EventType::Ok);
770        assert!(matches!(event.payload, EventPayload::None));
771        assert!(event.attributes.is_empty());
772    }
773
774    #[test]
775    fn test_event_with_attribute() {
776        let event = MeshCoreEvent::new(EventType::Ok, EventPayload::None)
777            .with_attribute("key1", "value1")
778            .with_attribute("key2", "value2");
779
780        assert_eq!(event.attributes.get("key1"), Some(&"value1".to_string()));
781        assert_eq!(event.attributes.get("key2"), Some(&"value2".to_string()));
782    }
783
784    #[test]
785    fn test_event_ok() {
786        let event = MeshCoreEvent::ok();
787        assert_eq!(event.event_type, EventType::Ok);
788        assert!(matches!(event.payload, EventPayload::None));
789    }
790
791    #[test]
792    fn test_event_error() {
793        let event = MeshCoreEvent::error("test error");
794        assert_eq!(event.event_type, EventType::Error);
795        match event.payload {
796            EventPayload::String(s) => assert_eq!(s, "test error"),
797            _ => panic!("Expected String payload"),
798        }
799    }
800
801    #[test]
802    fn test_event_matches_filters_empty() {
803        let event = MeshCoreEvent::new(EventType::Ok, EventPayload::None);
804        let filters = HashMap::new();
805        assert!(event.matches_filters(&filters));
806    }
807
808    #[test]
809    fn test_event_matches_filters_match() {
810        let event =
811            MeshCoreEvent::new(EventType::Ok, EventPayload::None).with_attribute("tag", "abc123");
812
813        let mut filters = HashMap::new();
814        filters.insert("tag".to_string(), "abc123".to_string());
815        assert!(event.matches_filters(&filters));
816    }
817
818    #[test]
819    fn test_event_matches_filters_no_match() {
820        let event =
821            MeshCoreEvent::new(EventType::Ok, EventPayload::None).with_attribute("tag", "abc123");
822
823        let mut filters = HashMap::new();
824        filters.insert("tag".to_string(), "xyz789".to_string());
825        assert!(!event.matches_filters(&filters));
826    }
827
828    #[test]
829    fn test_event_matches_filters_missing_attr() {
830        let event = MeshCoreEvent::new(EventType::Ok, EventPayload::None);
831
832        let mut filters = HashMap::new();
833        filters.insert("tag".to_string(), "abc123".to_string());
834        assert!(!event.matches_filters(&filters));
835    }
836
837    #[test]
838    fn test_contact_prefix() {
839        let contact = Contact {
840            public_key: [
841                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
842                0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,
843                0x1D, 0x1E, 0x1F, 0x20,
844            ],
845            contact_type: 1,
846            flags: 0,
847            path_len: -1,
848            out_path: Vec::new(),
849            adv_name: "Test".to_string(),
850            last_advert: 0,
851            adv_lat: 0,
852            adv_lon: 0,
853            last_modification_timestamp: 0,
854        };
855
856        assert_eq!(contact.prefix(), [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
857    }
858
859    #[test]
860    fn test_contact_public_key_hex() {
861        let mut public_key = [0u8; 32];
862        public_key[0..4].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
863
864        let contact = Contact {
865            public_key,
866            contact_type: 1,
867            flags: 0,
868            path_len: -1,
869            out_path: Vec::new(),
870            adv_name: "Test".to_string(),
871            last_advert: 0,
872            adv_lat: 0,
873            adv_lon: 0,
874            last_modification_timestamp: 0,
875        };
876
877        assert!(contact.public_key_hex().starts_with("deadbeef"));
878    }
879
880    #[test]
881    fn test_contact_prefix_hex() {
882        let mut public_key = [0u8; 32];
883        public_key[0..6].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02]);
884
885        let contact = Contact {
886            public_key,
887            contact_type: 1,
888            flags: 0,
889            path_len: -1,
890            out_path: Vec::new(),
891            adv_name: "Test".to_string(),
892            last_advert: 0,
893            adv_lat: 0,
894            adv_lon: 0,
895            last_modification_timestamp: 0,
896        };
897
898        assert_eq!(contact.prefix_hex(), "deadbeef0102");
899    }
900
901    #[test]
902    fn test_contact_latitude() {
903        let contact = Contact {
904            public_key: [0u8; 32],
905            contact_type: 1,
906            flags: 0,
907            path_len: -1,
908            out_path: Vec::new(),
909            adv_name: "Test".to_string(),
910            last_advert: 0,
911            adv_lat: 37774900, // 37.7749 degrees
912            adv_lon: 0,
913            last_modification_timestamp: 0,
914        };
915
916        assert!((contact.latitude() - 37.7749).abs() < 0.0001);
917    }
918
919    #[test]
920    fn test_contact_longitude() {
921        let contact = Contact {
922            public_key: [0u8; 32],
923            contact_type: 1,
924            flags: 0,
925            path_len: -1,
926            out_path: Vec::new(),
927            adv_name: "Test".to_string(),
928            last_advert: 0,
929            adv_lat: 0,
930            adv_lon: -122419400, // -122.4194 degrees
931            last_modification_timestamp: 0,
932        };
933
934        assert!((contact.longitude() - (-122.4194)).abs() < 0.0001);
935    }
936
937    #[tokio::test]
938    async fn test_event_dispatcher_new() {
939        let dispatcher = EventDispatcher::new();
940        // Just verify it can be created
941        let _receiver = dispatcher.receiver();
942    }
943
944    #[tokio::test]
945    async fn test_event_dispatcher_default() {
946        let dispatcher = EventDispatcher::default();
947        let _receiver = dispatcher.receiver();
948    }
949
950    #[tokio::test]
951    async fn test_event_dispatcher_emit() {
952        let dispatcher = EventDispatcher::new();
953        let mut receiver = dispatcher.receiver();
954
955        dispatcher.emit(MeshCoreEvent::ok()).await;
956
957        let received = tokio::time::timeout(Duration::from_millis(100), receiver.recv())
958            .await
959            .unwrap()
960            .unwrap();
961
962        assert_eq!(received.event_type, EventType::Ok);
963    }
964
965    #[tokio::test]
966    async fn test_event_dispatcher_subscribe() {
967        let dispatcher = Arc::new(EventDispatcher::new());
968        let call_count = Arc::new(AtomicUsize::new(0));
969        let call_count_clone = call_count.clone();
970
971        let _subscription = dispatcher
972            .subscribe(EventType::Ok, HashMap::new(), move |_event| {
973                call_count_clone.fetch_add(1, Ordering::SeqCst);
974            })
975            .await;
976
977        dispatcher.emit(MeshCoreEvent::ok()).await;
978
979        // Give time for callback to execute
980        tokio::time::sleep(Duration::from_millis(10)).await;
981
982        assert_eq!(call_count.load(Ordering::SeqCst), 1);
983    }
984
985    #[tokio::test]
986    async fn test_event_dispatcher_subscribe_with_filter() {
987        let dispatcher = Arc::new(EventDispatcher::new());
988        let call_count = Arc::new(AtomicUsize::new(0));
989        let call_count_clone = call_count.clone();
990
991        let mut filters = HashMap::new();
992        filters.insert("tag".to_string(), "match".to_string());
993
994        let _subscription = dispatcher
995            .subscribe(EventType::Ack, filters, move |_event| {
996                call_count_clone.fetch_add(1, Ordering::SeqCst);
997            })
998            .await;
999
1000        // This should NOT trigger callback (wrong filter)
1001        dispatcher
1002            .emit(
1003                MeshCoreEvent::new(EventType::Ack, EventPayload::None)
1004                    .with_attribute("tag", "nomatch"),
1005            )
1006            .await;
1007
1008        // This SHOULD trigger the callback
1009        dispatcher
1010            .emit(
1011                MeshCoreEvent::new(EventType::Ack, EventPayload::None)
1012                    .with_attribute("tag", "match"),
1013            )
1014            .await;
1015
1016        tokio::time::sleep(Duration::from_millis(10)).await;
1017
1018        assert_eq!(call_count.load(Ordering::SeqCst), 1);
1019    }
1020
1021    #[tokio::test]
1022    async fn test_event_dispatcher_unsubscribe() {
1023        let dispatcher = Arc::new(EventDispatcher::new());
1024        let call_count = Arc::new(AtomicUsize::new(0));
1025        let call_count_clone = call_count.clone();
1026
1027        let subscription = dispatcher
1028            .subscribe(EventType::Ok, HashMap::new(), move |_event| {
1029                call_count_clone.fetch_add(1, Ordering::SeqCst);
1030            })
1031            .await;
1032
1033        // First emit should trigger
1034        dispatcher.emit(MeshCoreEvent::ok()).await;
1035        tokio::time::sleep(Duration::from_millis(10)).await;
1036        assert_eq!(call_count.load(Ordering::SeqCst), 1);
1037
1038        // Unsubscribe
1039        subscription.unsubscribe().await;
1040
1041        // This call to emit() should process unsubscription
1042        dispatcher.emit(MeshCoreEvent::ok()).await;
1043        tokio::time::sleep(Duration::from_millis(10)).await;
1044
1045        // Third emit should NOT trigger (unsubscribed)
1046        dispatcher.emit(MeshCoreEvent::ok()).await;
1047        tokio::time::sleep(Duration::from_millis(10)).await;
1048
1049        // Should still be 2 (second emit counted, third did not)
1050        assert!(call_count.load(Ordering::SeqCst) <= 2);
1051    }
1052
1053    #[tokio::test]
1054    async fn test_event_dispatcher_wait_for_event() {
1055        let dispatcher = Arc::new(EventDispatcher::new());
1056        let dispatcher_clone = dispatcher.clone();
1057
1058        // Spawn a task that emits after a short delay
1059        tokio::spawn(async move {
1060            tokio::time::sleep(Duration::from_millis(10)).await;
1061            dispatcher_clone.emit(MeshCoreEvent::ok()).await;
1062        });
1063
1064        let result = dispatcher
1065            .wait_for_event(
1066                Some(EventType::Ok),
1067                HashMap::new(),
1068                Duration::from_millis(100),
1069            )
1070            .await;
1071
1072        assert!(result.is_some());
1073        assert_eq!(result.unwrap().event_type, EventType::Ok);
1074    }
1075
1076    #[tokio::test]
1077    async fn test_event_dispatcher_wait_for_event_timeout() {
1078        let dispatcher = EventDispatcher::new();
1079
1080        let result = dispatcher
1081            .wait_for_event(
1082                Some(EventType::Ok),
1083                HashMap::new(),
1084                Duration::from_millis(10),
1085            )
1086            .await;
1087
1088        assert!(result.is_none());
1089    }
1090
1091    #[tokio::test]
1092    async fn test_event_dispatcher_wait_for_event_with_filter() {
1093        let dispatcher = Arc::new(EventDispatcher::new());
1094        let dispatcher_clone = dispatcher.clone();
1095
1096        // Spawn a task that emits events
1097        tokio::spawn(async move {
1098            tokio::time::sleep(Duration::from_millis(5)).await;
1099            // First, emit with the wrong filter
1100            dispatcher_clone
1101                .emit(
1102                    MeshCoreEvent::new(EventType::Ack, EventPayload::None)
1103                        .with_attribute("tag", "wrong"),
1104                )
1105                .await;
1106            tokio::time::sleep(Duration::from_millis(5)).await;
1107            // Then emit with the correct filter
1108            dispatcher_clone
1109                .emit(
1110                    MeshCoreEvent::new(EventType::Ack, EventPayload::None)
1111                        .with_attribute("tag", "correct"),
1112                )
1113                .await;
1114        });
1115
1116        let mut filters = HashMap::new();
1117        filters.insert("tag".to_string(), "correct".to_string());
1118
1119        let result = dispatcher
1120            .wait_for_event(Some(EventType::Ack), filters, Duration::from_millis(100))
1121            .await;
1122
1123        assert!(result.is_some());
1124        assert_eq!(
1125            result.unwrap().attributes.get("tag"),
1126            Some(&"correct".to_string())
1127        );
1128    }
1129
1130    #[test]
1131    fn test_event_type_debug() {
1132        assert_eq!(format!("{:?}", EventType::Connected), "Connected");
1133        assert_eq!(format!("{:?}", EventType::Disconnected), "Disconnected");
1134    }
1135
1136    #[test]
1137    fn test_event_type_clone_eq() {
1138        let e1 = EventType::SelfInfo;
1139        let e2 = e1;
1140        assert_eq!(e1, e2);
1141    }
1142
1143    #[test]
1144    fn test_event_payload_clone() {
1145        let payload = EventPayload::String("test".to_string());
1146        let cloned = payload.clone();
1147        match cloned {
1148            EventPayload::String(s) => assert_eq!(s, "test"),
1149            _ => panic!("Wrong payload type"),
1150        }
1151    }
1152
1153    #[test]
1154    fn test_stats_category_eq() {
1155        assert_eq!(StatsCategory::Core, StatsCategory::Core);
1156        assert_ne!(StatsCategory::Core, StatsCategory::Radio);
1157    }
1158
1159    #[test]
1160    fn test_self_info_clone() {
1161        let info = SelfInfo {
1162            adv_type: 1,
1163            tx_power: 20,
1164            max_tx_power: 30,
1165            public_key: [0u8; 32],
1166            adv_lat: 0,
1167            adv_lon: 0,
1168            multi_acks: 0,
1169            adv_loc_policy: 0,
1170            telemetry_mode_base: 0,
1171            telemetry_mode_loc: 0,
1172            telemetry_mode_env: 0,
1173            manual_add_contacts: false,
1174            radio_freq: 915000000,
1175            radio_bw: 125000,
1176            sf: 7,
1177            cr: 5,
1178            name: "Test".to_string(),
1179        };
1180
1181        let cloned = info.clone();
1182        assert_eq!(cloned.tx_power, 20);
1183        assert_eq!(cloned.name, "Test");
1184    }
1185
1186    #[test]
1187    fn test_contact_message_clone() {
1188        let msg = ContactMessage {
1189            sender_prefix: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
1190            path_len: 2,
1191            txt_type: 1,
1192            sender_timestamp: 1234567890,
1193            text: "Hello".to_string(),
1194            snr: Some(10.0),
1195            signature: None,
1196        };
1197
1198        let cloned = msg.clone();
1199        assert_eq!(cloned.text, "Hello");
1200        assert_eq!(cloned.snr, Some(10.0));
1201    }
1202
1203    #[test]
1204    fn test_contact_message_message_id() {
1205        let msg = ContactMessage {
1206            sender_prefix: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
1207            path_len: 2,
1208            txt_type: 1,
1209            sender_timestamp: 0x12345678,
1210            text: "Hello".to_string(),
1211            snr: None,
1212            signature: None,
1213        };
1214
1215        let id = msg.message_id();
1216        // First 4 bytes of sender_prefix + timestamp bytes
1217        // 0x01020304 | 0x12345678 as big-endian
1218        assert_eq!(id, 0x0102030412345678);
1219    }
1220
1221    #[test]
1222    fn test_contact_message_sender_prefix_hex() {
1223        let msg = ContactMessage {
1224            sender_prefix: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
1225            path_len: 0,
1226            txt_type: 0,
1227            sender_timestamp: 0,
1228            text: "".to_string(),
1229            snr: None,
1230            signature: None,
1231        };
1232
1233        assert_eq!(msg.sender_prefix_hex(), "aabbccddeeff");
1234    }
1235
1236    #[test]
1237    fn test_channel_message_clone() {
1238        let msg = ChannelMessage {
1239            channel_idx: 5,
1240            path_len: 1,
1241            txt_type: 0,
1242            sender_timestamp: 1234567890,
1243            text: "Channel msg".to_string(),
1244            snr: Some(8.5),
1245        };
1246
1247        let cloned = msg.clone();
1248        assert_eq!(cloned.channel_idx, 5);
1249        assert_eq!(cloned.text, "Channel msg");
1250    }
1251
1252    #[test]
1253    fn test_channel_message_message_id() {
1254        let msg = ChannelMessage {
1255            channel_idx: 5,
1256            path_len: 1,
1257            txt_type: 0,
1258            sender_timestamp: 0x12345678,
1259            text: "".to_string(),
1260            snr: None,
1261        };
1262
1263        let id = msg.message_id();
1264        // channel_idx in first byte, timestamp in last 4 bytes
1265        // 0x05_00_00_00_12345678
1266        assert_eq!(id, 0x0500000012345678);
1267    }
1268
1269    #[test]
1270    fn test_battery_info_debug() {
1271        let info = BatteryInfo {
1272            battery_mv: 4200,
1273            used_kb: Some(512),
1274            total_kb: Some(4096),
1275        };
1276        let debug_str = format!("{:?}", info);
1277        assert!(debug_str.contains("4200"));
1278    }
1279
1280    #[test]
1281    fn test_battery_info_voltage() {
1282        let info = BatteryInfo {
1283            battery_mv: 3700,
1284            used_kb: None,
1285            total_kb: None,
1286        };
1287        assert!((info.voltage() - 3.7).abs() < 0.001);
1288    }
1289
1290    #[test]
1291    fn test_battery_info_no_storage() {
1292        let info = BatteryInfo {
1293            battery_mv: 4100,
1294            used_kb: None,
1295            total_kb: None,
1296        };
1297        assert_eq!(info.battery_mv, 4100);
1298        assert!(info.used_kb.is_none());
1299        assert!(info.total_kb.is_none());
1300    }
1301
1302    #[test]
1303    fn test_battery_info_percentage_full() {
1304        let info = BatteryInfo {
1305            battery_mv: 3930,
1306            used_kb: None,
1307            total_kb: None,
1308        };
1309        assert_eq!(info.percentage(), 100);
1310    }
1311
1312    #[test]
1313    fn test_battery_info_percentage_empty() {
1314        let info = BatteryInfo {
1315            battery_mv: 3000,
1316            used_kb: None,
1317            total_kb: None,
1318        };
1319        assert_eq!(info.percentage(), 0);
1320    }
1321
1322    #[test]
1323    fn test_battery_info_percentage_half() {
1324        let info = BatteryInfo {
1325            battery_mv: 3465, // midpoint between 3000 and 3930
1326            used_kb: None,
1327            total_kb: None,
1328        };
1329        assert_eq!(info.percentage(), 50);
1330    }
1331
1332    #[test]
1333    fn test_battery_info_percentage_below_min() {
1334        let info = BatteryInfo {
1335            battery_mv: 2800,
1336            used_kb: None,
1337            total_kb: None,
1338        };
1339        assert_eq!(info.percentage(), 0);
1340    }
1341
1342    #[test]
1343    fn test_battery_info_percentage_above_max() {
1344        let info = BatteryInfo {
1345            battery_mv: 4200,
1346            used_kb: None,
1347            total_kb: None,
1348        };
1349        assert_eq!(info.percentage(), 100);
1350    }
1351
1352    #[test]
1353    fn test_channel_info_data_clone() {
1354        let info = ChannelInfoData {
1355            channel_idx: 1,
1356            name: "General".to_string(),
1357            secret: [0xAA; CHANNEL_SECRET_LEN],
1358        };
1359        let cloned = info.clone();
1360        assert_eq!(cloned.channel_idx, 1);
1361        assert_eq!(cloned.name, "General");
1362    }
1363
1364    #[test]
1365    fn test_advertisement_data_debug() {
1366        let advert = AdvertisementData {
1367            prefix: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
1368            name: "Node1".to_string(),
1369            lat: 37774900,
1370            lon: -122419400,
1371        };
1372        let debug_str = format!("{:?}", advert);
1373        assert!(debug_str.contains("Node1"));
1374    }
1375
1376    #[test]
1377    fn test_path_update_data_clone() {
1378        let update = PathUpdateData {
1379            prefix: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
1380            path_len: 3,
1381            path: vec![0x0A, 0x0B, 0x0C],
1382        };
1383        let cloned = update.clone();
1384        assert_eq!(cloned.path_len, 3);
1385        assert_eq!(cloned.path, vec![0x0A, 0x0B, 0x0C]);
1386    }
1387
1388    #[test]
1389    fn test_trace_hop_clone() {
1390        let hop = TraceHop {
1391            prefix: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
1392            snr: 10.5,
1393        };
1394        let cloned = hop.clone();
1395        assert_eq!(cloned.snr, 10.5);
1396    }
1397
1398    #[test]
1399    fn test_neighbour_clone() {
1400        let neighbour = Neighbour {
1401            pubkey: vec![0x01, 0x02, 0x03],
1402            secs_ago: 300,
1403            snr: 8.0,
1404        };
1405        let cloned = neighbour.clone();
1406        assert_eq!(cloned.secs_ago, 300);
1407    }
1408
1409    #[test]
1410    fn test_discover_entry_clone() {
1411        let entry = DiscoverEntry {
1412            pubkey: vec![0x01, 0x02, 0x03],
1413            name: "Node".to_string(),
1414        };
1415        let cloned = entry.clone();
1416        assert_eq!(cloned.name, "Node");
1417    }
1418
1419    #[test]
1420    fn test_event_payload_variants() {
1421        // Test various payload types
1422        let _none = EventPayload::None;
1423        let _string = EventPayload::String("test".to_string());
1424        let _bytes = EventPayload::Bytes(vec![1, 2, 3]);
1425        let _time = EventPayload::Time(1234567890);
1426        let _private_key = EventPayload::PrivateKey([0u8; 64]);
1427        let _signature = EventPayload::Signature(vec![1, 2, 3, 4]);
1428        let _sign_start = EventPayload::SignStart { max_length: 1000 };
1429        let _ack = EventPayload::Ack {
1430            tag: [0x01, 0x02, 0x03, 0x04],
1431        };
1432        let _binary = EventPayload::BinaryResponse {
1433            tag: [0x01, 0x02, 0x03, 0x04],
1434            data: vec![5, 6, 7, 8],
1435        };
1436        let _auto_add = EventPayload::AutoAddConfig { flags: 0x01 };
1437        let _log_data = EventPayload::LogData(LogData {
1438            snr: 10.5,
1439            rssi: -80,
1440            payload: vec![0x01, 0x02, 0x03],
1441        });
1442    }
1443
1444    #[test]
1445    fn test_log_data_clone() {
1446        let log_data = LogData {
1447            snr: 12.25,
1448            rssi: -75,
1449            payload: vec![0xAA, 0xBB, 0xCC],
1450        };
1451        let cloned = log_data.clone();
1452        assert_eq!(cloned.snr, 12.25);
1453        assert_eq!(cloned.rssi, -75);
1454        assert_eq!(cloned.payload, vec![0xAA, 0xBB, 0xCC]);
1455    }
1456
1457    #[test]
1458    fn test_log_data_debug() {
1459        let log_data = LogData {
1460            snr: 5.5,
1461            rssi: -90,
1462            payload: vec![0x01, 0x02],
1463        };
1464        let debug_str = format!("{:?}", log_data);
1465        assert!(debug_str.contains("snr"));
1466        assert!(debug_str.contains("rssi"));
1467        assert!(debug_str.contains("payload"));
1468    }
1469
1470    #[test]
1471    fn test_log_data_snr_conversion() {
1472        // SNR is stored as signed byte / 4.0
1473        // So SNR byte 40 = 10.0, SNR byte -40 = -10.0
1474        let log_data = LogData {
1475            snr: 10.0, // Would be byte value 40
1476            rssi: -85,
1477            payload: vec![],
1478        };
1479        assert_eq!(log_data.snr, 10.0);
1480    }
1481
1482    #[test]
1483    fn test_log_data_negative_snr() {
1484        let log_data = LogData {
1485            snr: -5.25, // Would be byte value -21
1486            rssi: -100,
1487            payload: vec![0x01],
1488        };
1489        assert_eq!(log_data.snr, -5.25);
1490    }
1491
1492    #[test]
1493    fn test_log_data_empty_payload() {
1494        let log_data = LogData {
1495            snr: 0.0,
1496            rssi: 0,
1497            payload: vec![],
1498        };
1499        assert!(log_data.payload.is_empty());
1500    }
1501}