Skip to main content

offline_protocol/telemetry/
device.rs

1//! Push-based snapshots of local device capability changes.
2//!
3//! Emitted when any of (battery level, charging state, relay role) changes
4//! between consecutive `OfflineProtocol::process()` ticks. Apps that need
5//! "always-on" polling consume this instead of reconstructing the state from
6//! the cumulative `TransportMetrics` stream.
7//!
8//! Advertised services are **not** represented — the SDK does not currently
9//! track an advertised-services list. A later step introduces that surface
10//! with its own dedicated record.
11
12use offline_protocol_router::RelayRole;
13
14/// Bitmask bit indicating `battery_level` changed between ticks.
15pub const CHANGED_BATTERY: u8 = 0b0000_0001;
16/// Bitmask bit indicating `is_charging` changed between ticks.
17pub const CHANGED_CHARGING: u8 = 0b0000_0010;
18/// Bitmask bit indicating `relay_role` changed between ticks.
19pub const CHANGED_RELAY_ROLE: u8 = 0b0000_0100;
20
21/// Snapshot of local device capability at the moment of emission.
22///
23/// The `changed_fields` bitmask identifies which fields triggered emission —
24/// sinks can filter on a specific bit to ignore unrelated churn.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct DeviceCapabilitySnapshot {
27    /// Emission timestamp (milliseconds since Unix epoch).
28    pub timestamp_ms: i64,
29    /// Current battery level (0-100), or `None` if unknown.
30    pub battery_level: Option<u8>,
31    /// Whether the device is charging.
32    pub is_charging: bool,
33    /// Current relay role (regular or relay).
34    pub relay_role: RelayRole,
35    /// Bitmask of the fields that triggered this emission.
36    pub changed_fields: u8,
37}
38
39impl DeviceCapabilitySnapshot {
40    /// Stable OTel-friendly name for this record.
41    pub const NAME: &'static str = "protocol.device.capability_changed";
42
43    /// Returns the stable telemetry name.
44    pub fn telemetry_name(&self) -> &'static str {
45        Self::NAME
46    }
47
48    /// Returns `true` when the given change bit is set.
49    pub fn has_changed(&self, bit: u8) -> bool {
50        self.changed_fields & bit != 0
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn name_is_stable() {
60        let snapshot = DeviceCapabilitySnapshot {
61            timestamp_ms: 0,
62            battery_level: Some(50),
63            is_charging: false,
64            relay_role: RelayRole::Regular,
65            changed_fields: CHANGED_BATTERY,
66        };
67        assert_eq!(
68            DeviceCapabilitySnapshot::NAME,
69            "protocol.device.capability_changed"
70        );
71        assert_eq!(
72            snapshot.telemetry_name(),
73            "protocol.device.capability_changed"
74        );
75    }
76
77    #[test]
78    fn has_changed_respects_bits() {
79        let snapshot = DeviceCapabilitySnapshot {
80            timestamp_ms: 0,
81            battery_level: None,
82            is_charging: true,
83            relay_role: RelayRole::Regular,
84            changed_fields: CHANGED_CHARGING | CHANGED_RELAY_ROLE,
85        };
86        assert!(snapshot.has_changed(CHANGED_CHARGING));
87        assert!(snapshot.has_changed(CHANGED_RELAY_ROLE));
88        assert!(!snapshot.has_changed(CHANGED_BATTERY));
89    }
90}