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}