Skip to main content

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    /// Refresh the aranet-service status.
93    RefreshServiceStatus,
94
95    /// Start the aranet-service collector.
96    StartServiceCollector,
97
98    /// Stop the aranet-service collector.
99    StopServiceCollector,
100
101    /// Set a friendly alias/name for a device.
102    SetAlias {
103        /// The device identifier.
104        device_id: String,
105        /// The new alias (or None to clear).
106        alias: Option<String>,
107    },
108
109    /// Forget (remove) a device from the known devices list and store.
110    ForgetDevice {
111        /// The device identifier.
112        device_id: String,
113    },
114
115    /// Shut down the worker thread.
116    Shutdown,
117}
118
119/// Cached device data loaded from the store.
120#[derive(Debug, Clone)]
121pub struct CachedDevice {
122    /// Device identifier.
123    pub id: String,
124    /// Device name.
125    pub name: Option<String>,
126    /// Device type.
127    pub device_type: Option<DeviceType>,
128    /// Latest reading, if available.
129    pub reading: Option<CurrentReading>,
130    /// When history was last synced.
131    pub last_sync: Option<time::OffsetDateTime>,
132}
133
134/// Events sent from the background worker to the UI thread.
135///
136/// These events represent the results of background operations
137/// and are used to update the UI state.
138#[derive(Debug, Clone)]
139pub enum SensorEvent {
140    /// Cached data loaded from the store on startup.
141    CachedDataLoaded {
142        /// Cached devices with their latest readings.
143        devices: Vec<CachedDevice>,
144    },
145
146    /// A device scan has started.
147    ScanStarted,
148
149    /// A device scan has completed successfully.
150    ScanComplete {
151        /// The list of discovered devices.
152        devices: Vec<DiscoveredDevice>,
153    },
154
155    /// A device scan failed.
156    ScanError {
157        /// Description of the error.
158        error: String,
159    },
160
161    /// Attempting to connect to a device.
162    DeviceConnecting {
163        /// The device identifier.
164        device_id: String,
165    },
166
167    /// Successfully connected to a device.
168    DeviceConnected {
169        /// The device identifier.
170        device_id: String,
171        /// The device name, if available.
172        name: Option<String>,
173        /// The device type, if detected.
174        device_type: Option<DeviceType>,
175        /// RSSI signal strength in dBm.
176        rssi: Option<i16>,
177    },
178
179    /// Disconnected from a device.
180    DeviceDisconnected {
181        /// The device identifier.
182        device_id: String,
183    },
184
185    /// Failed to connect to a device.
186    ConnectionError {
187        /// The device identifier.
188        device_id: String,
189        /// Description of the error.
190        error: String,
191    },
192
193    /// Received an updated reading from a device.
194    ReadingUpdated {
195        /// The device identifier.
196        device_id: String,
197        /// The current sensor reading.
198        reading: CurrentReading,
199    },
200
201    /// Failed to read from a device.
202    ReadingError {
203        /// The device identifier.
204        device_id: String,
205        /// Description of the error.
206        error: String,
207    },
208
209    /// Historical data loaded for a device.
210    HistoryLoaded {
211        /// The device identifier.
212        device_id: String,
213        /// The historical records.
214        records: Vec<HistoryRecord>,
215    },
216
217    /// History sync started for a device.
218    HistorySyncStarted {
219        /// The device identifier.
220        device_id: String,
221    },
222
223    /// History sync completed for a device.
224    HistorySynced {
225        /// The device identifier.
226        device_id: String,
227        /// Number of records synced.
228        count: usize,
229    },
230
231    /// History sync failed for a device.
232    HistorySyncError {
233        /// The device identifier.
234        device_id: String,
235        /// Description of the error.
236        error: String,
237    },
238
239    /// Measurement interval changed for a device.
240    IntervalChanged {
241        /// The device identifier.
242        device_id: String,
243        /// The new interval in seconds.
244        interval_secs: u16,
245    },
246
247    /// Failed to set measurement interval.
248    IntervalError {
249        /// The device identifier.
250        device_id: String,
251        /// Description of the error.
252        error: String,
253    },
254
255    /// Device settings loaded from the device.
256    SettingsLoaded {
257        /// The device identifier.
258        device_id: String,
259        /// The device settings.
260        settings: DeviceSettings,
261    },
262
263    /// Bluetooth range changed for a device.
264    BluetoothRangeChanged {
265        /// The device identifier.
266        device_id: String,
267        /// Whether extended range is now enabled.
268        extended: bool,
269    },
270
271    /// Failed to set Bluetooth range.
272    BluetoothRangeError {
273        /// The device identifier.
274        device_id: String,
275        /// Description of the error.
276        error: String,
277    },
278
279    /// Smart Home setting changed for a device.
280    SmartHomeChanged {
281        /// The device identifier.
282        device_id: String,
283        /// Whether Smart Home mode is now enabled.
284        enabled: bool,
285    },
286
287    /// Failed to set Smart Home mode.
288    SmartHomeError {
289        /// The device identifier.
290        device_id: String,
291        /// Description of the error.
292        error: String,
293    },
294
295    /// Service status refreshed successfully.
296    ServiceStatusRefreshed {
297        /// Whether the service is reachable.
298        reachable: bool,
299        /// Whether the collector is running.
300        collector_running: bool,
301        /// Service uptime in seconds.
302        uptime_seconds: Option<u64>,
303        /// Monitored devices with their collection stats.
304        devices: Vec<ServiceDeviceStats>,
305    },
306
307    /// Service status refresh failed.
308    ServiceStatusError {
309        /// Description of the error.
310        error: String,
311    },
312
313    /// Service collector started successfully.
314    ServiceCollectorStarted,
315
316    /// Service collector stopped successfully.
317    ServiceCollectorStopped,
318
319    /// Service collector action failed.
320    ServiceCollectorError {
321        /// Description of the error.
322        error: String,
323    },
324
325    /// Device alias changed successfully.
326    AliasChanged {
327        /// The device identifier.
328        device_id: String,
329        /// The new alias (or None if cleared).
330        alias: Option<String>,
331    },
332
333    /// Failed to set device alias.
334    AliasError {
335        /// The device identifier.
336        device_id: String,
337        /// Description of the error.
338        error: String,
339    },
340
341    /// Device was forgotten (removed from known devices).
342    DeviceForgotten {
343        /// The device identifier.
344        device_id: String,
345    },
346
347    /// Failed to forget device.
348    ForgetDeviceError {
349        /// The device identifier.
350        device_id: String,
351        /// Description of the error.
352        error: String,
353    },
354}
355
356/// Statistics for a device being monitored by the service.
357#[derive(Debug, Clone)]
358pub struct ServiceDeviceStats {
359    /// Device identifier.
360    pub device_id: String,
361    /// Device alias/name.
362    pub alias: Option<String>,
363    /// Poll interval in seconds.
364    pub poll_interval: u64,
365    /// Whether the device is currently being polled.
366    pub polling: bool,
367    /// Number of successful polls.
368    pub success_count: u64,
369    /// Number of failed polls.
370    pub failure_count: u64,
371    /// Last poll time.
372    pub last_poll_at: Option<time::OffsetDateTime>,
373    /// Last error message.
374    pub last_error: Option<String>,
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380
381    #[test]
382    fn test_command_debug() {
383        let cmd = Command::Scan {
384            duration: Duration::from_secs(5),
385        };
386        let debug = format!("{:?}", cmd);
387        assert!(debug.contains("Scan"));
388        assert!(debug.contains("5"));
389    }
390
391    #[test]
392    fn test_command_clone() {
393        let cmd = Command::Connect {
394            device_id: "test-device".to_string(),
395        };
396        let cloned = cmd.clone();
397        match cloned {
398            Command::Connect { device_id } => assert_eq!(device_id, "test-device"),
399            _ => panic!("Expected Connect variant"),
400        }
401    }
402
403    #[test]
404    fn test_sensor_event_debug() {
405        let event = SensorEvent::ScanStarted;
406        let debug = format!("{:?}", event);
407        assert!(debug.contains("ScanStarted"));
408    }
409
410    #[test]
411    fn test_cached_device_default_values() {
412        let device = CachedDevice {
413            id: "test".to_string(),
414            name: None,
415            device_type: None,
416            reading: None,
417            last_sync: None,
418        };
419        assert_eq!(device.id, "test");
420        assert!(device.name.is_none());
421    }
422}