aranet_core/
messages.rs

1//! Message types for UI/worker communication.
2//!
3//! This module defines the command and event enums used for bidirectional
4//! communication between UI threads and background BLE workers. These types
5//! are shared between TUI and GUI applications.
6//!
7//! # Architecture
8//!
9//! ```text
10//! +------------------+     Command      +------------------+
11//! |    UI Thread     | --------------> |  SensorWorker    |
12//! |  (egui/ratatui)  |                 |  (tokio runtime) |
13//! |                  | <-------------- |                  |
14//! +------------------+   SensorEvent   +------------------+
15//! ```
16//!
17//! - [`Command`]: Messages sent from the UI thread to the background worker
18//! - [`SensorEvent`]: Events sent from the worker back to the UI thread
19
20use std::time::Duration;
21
22use crate::DiscoveredDevice;
23use crate::settings::DeviceSettings;
24use aranet_types::{CurrentReading, DeviceType, HistoryRecord};
25
26/// Commands sent from the UI thread to the background worker.
27///
28/// These commands represent user-initiated actions that require
29/// Bluetooth operations or other background processing.
30#[derive(Debug, Clone)]
31pub enum Command {
32    /// Load cached devices and readings from the store on startup.
33    LoadCachedData,
34
35    /// Scan for nearby Aranet devices.
36    Scan {
37        /// How long to scan for devices.
38        duration: Duration,
39    },
40
41    /// Connect to a specific device.
42    Connect {
43        /// The device identifier to connect to.
44        device_id: String,
45    },
46
47    /// Disconnect from a specific device.
48    Disconnect {
49        /// The device identifier to disconnect from.
50        device_id: String,
51    },
52
53    /// Refresh the current reading for a single device.
54    RefreshReading {
55        /// The device identifier to refresh.
56        device_id: String,
57    },
58
59    /// Refresh readings for all connected devices.
60    RefreshAll,
61
62    /// Sync history from device (download from BLE and save to store).
63    SyncHistory {
64        /// The device identifier to sync history for.
65        device_id: String,
66    },
67
68    /// Set the measurement interval for a device.
69    SetInterval {
70        /// The device identifier.
71        device_id: String,
72        /// The new interval in seconds.
73        interval_secs: u16,
74    },
75
76    /// Set the Bluetooth range for a device.
77    SetBluetoothRange {
78        /// The device identifier.
79        device_id: String,
80        /// Whether to use extended range (true) or standard (false).
81        extended: bool,
82    },
83
84    /// Set Smart Home integration mode for a device.
85    SetSmartHome {
86        /// The device identifier.
87        device_id: String,
88        /// Whether to enable Smart Home mode.
89        enabled: bool,
90    },
91
92    /// Shut down the worker thread.
93    Shutdown,
94}
95
96/// Cached device data loaded from the store.
97#[derive(Debug, Clone)]
98pub struct CachedDevice {
99    /// Device identifier.
100    pub id: String,
101    /// Device name.
102    pub name: Option<String>,
103    /// Device type.
104    pub device_type: Option<DeviceType>,
105    /// Latest reading, if available.
106    pub reading: Option<CurrentReading>,
107    /// When history was last synced.
108    pub last_sync: Option<time::OffsetDateTime>,
109}
110
111/// Events sent from the background worker to the UI thread.
112///
113/// These events represent the results of background operations
114/// and are used to update the UI state.
115#[derive(Debug, Clone)]
116pub enum SensorEvent {
117    /// Cached data loaded from the store on startup.
118    CachedDataLoaded {
119        /// Cached devices with their latest readings.
120        devices: Vec<CachedDevice>,
121    },
122
123    /// A device scan has started.
124    ScanStarted,
125
126    /// A device scan has completed successfully.
127    ScanComplete {
128        /// The list of discovered devices.
129        devices: Vec<DiscoveredDevice>,
130    },
131
132    /// A device scan failed.
133    ScanError {
134        /// Description of the error.
135        error: String,
136    },
137
138    /// Attempting to connect to a device.
139    DeviceConnecting {
140        /// The device identifier.
141        device_id: String,
142    },
143
144    /// Successfully connected to a device.
145    DeviceConnected {
146        /// The device identifier.
147        device_id: String,
148        /// The device name, if available.
149        name: Option<String>,
150        /// The device type, if detected.
151        device_type: Option<DeviceType>,
152        /// RSSI signal strength in dBm.
153        rssi: Option<i16>,
154    },
155
156    /// Disconnected from a device.
157    DeviceDisconnected {
158        /// The device identifier.
159        device_id: String,
160    },
161
162    /// Failed to connect to a device.
163    ConnectionError {
164        /// The device identifier.
165        device_id: String,
166        /// Description of the error.
167        error: String,
168    },
169
170    /// Received an updated reading from a device.
171    ReadingUpdated {
172        /// The device identifier.
173        device_id: String,
174        /// The current sensor reading.
175        reading: CurrentReading,
176    },
177
178    /// Failed to read from a device.
179    ReadingError {
180        /// The device identifier.
181        device_id: String,
182        /// Description of the error.
183        error: String,
184    },
185
186    /// Historical data loaded for a device.
187    HistoryLoaded {
188        /// The device identifier.
189        device_id: String,
190        /// The historical records.
191        records: Vec<HistoryRecord>,
192    },
193
194    /// History sync started for a device.
195    HistorySyncStarted {
196        /// The device identifier.
197        device_id: String,
198    },
199
200    /// History sync completed for a device.
201    HistorySynced {
202        /// The device identifier.
203        device_id: String,
204        /// Number of records synced.
205        count: usize,
206    },
207
208    /// History sync failed for a device.
209    HistorySyncError {
210        /// The device identifier.
211        device_id: String,
212        /// Description of the error.
213        error: String,
214    },
215
216    /// Measurement interval changed for a device.
217    IntervalChanged {
218        /// The device identifier.
219        device_id: String,
220        /// The new interval in seconds.
221        interval_secs: u16,
222    },
223
224    /// Failed to set measurement interval.
225    IntervalError {
226        /// The device identifier.
227        device_id: String,
228        /// Description of the error.
229        error: String,
230    },
231
232    /// Device settings loaded from the device.
233    SettingsLoaded {
234        /// The device identifier.
235        device_id: String,
236        /// The device settings.
237        settings: DeviceSettings,
238    },
239
240    /// Bluetooth range changed for a device.
241    BluetoothRangeChanged {
242        /// The device identifier.
243        device_id: String,
244        /// Whether extended range is now enabled.
245        extended: bool,
246    },
247
248    /// Failed to set Bluetooth range.
249    BluetoothRangeError {
250        /// The device identifier.
251        device_id: String,
252        /// Description of the error.
253        error: String,
254    },
255
256    /// Smart Home setting changed for a device.
257    SmartHomeChanged {
258        /// The device identifier.
259        device_id: String,
260        /// Whether Smart Home mode is now enabled.
261        enabled: bool,
262    },
263
264    /// Failed to set Smart Home mode.
265    SmartHomeError {
266        /// The device identifier.
267        device_id: String,
268        /// Description of the error.
269        error: String,
270    },
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn test_command_debug() {
279        let cmd = Command::Scan {
280            duration: Duration::from_secs(5),
281        };
282        let debug = format!("{:?}", cmd);
283        assert!(debug.contains("Scan"));
284        assert!(debug.contains("5"));
285    }
286
287    #[test]
288    fn test_command_clone() {
289        let cmd = Command::Connect {
290            device_id: "test-device".to_string(),
291        };
292        let cloned = cmd.clone();
293        match cloned {
294            Command::Connect { device_id } => assert_eq!(device_id, "test-device"),
295            _ => panic!("Expected Connect variant"),
296        }
297    }
298
299    #[test]
300    fn test_sensor_event_debug() {
301        let event = SensorEvent::ScanStarted;
302        let debug = format!("{:?}", event);
303        assert!(debug.contains("ScanStarted"));
304    }
305
306    #[test]
307    fn test_cached_device_default_values() {
308        let device = CachedDevice {
309            id: "test".to_string(),
310            name: None,
311            device_type: None,
312            reading: None,
313            last_sync: None,
314        };
315        assert_eq!(device.id, "test");
316        assert!(device.name.is_none());
317    }
318}