edge_core/device_control.rs
1//! Device-control abstraction surfaced over `/ws/edge`.
2//!
3//! `weave-server` may send `ServerToEdge::DisplayGlyph` /
4//! `ServerToEdge::DeviceConnect` / `ServerToEdge::DeviceDisconnect` frames
5//! to drive a specific device on the edge — used by the weave-web UI
6//! buttons (Connect / Disconnect / Test LED). The native edge-agent owns
7//! per-device handles (BLE for Nuimo, etc.) that are foreign to this crate,
8//! so the WS handler dispatches via this trait. The agent registers an
9//! impl backed by its device map; targets the trait doesn't recognise
10//! return `Ok(())` after logging.
11//!
12//! Errors propagate as `anyhow::Error` for symmetry with the rest of the
13//! WS client surface and to keep the trait dyn-friendly.
14
15use async_trait::async_trait;
16
17/// Trait the WS client uses to dispatch device-control frames. The
18/// concrete impl lives in the binary that owns the device handles
19/// (`edge-agent` for Nuimo today; future targets register their own).
20#[async_trait]
21pub trait DeviceControlHook: Send + Sync {
22 /// Render `pattern` on the device's display.
23 /// `pattern` is a 9-line ASCII grid (`*` = on).
24 /// `brightness` is on the closed interval `0.0..=1.0`; `None` =
25 /// implementation default.
26 /// `timeout_ms` is the auto-clear timeout; `None` = implementation
27 /// default.
28 /// `transition` is `"immediate"` or `"cross_fade"`; `None` =
29 /// implementation default.
30 async fn display_glyph(
31 &self,
32 device_type: &str,
33 device_id: &str,
34 pattern: &str,
35 brightness: Option<f32>,
36 timeout_ms: Option<u32>,
37 transition: Option<&str>,
38 ) -> anyhow::Result<()>;
39
40 /// Re-attempt a connection to the device. Implementations should clear
41 /// any "paused" flag that suppresses auto-reconnect.
42 async fn connect_device(&self, device_type: &str, device_id: &str) -> anyhow::Result<()>;
43
44 /// Drop the active connection and set "paused" so the auto-reconnect
45 /// loop does not immediately re-establish the link.
46 async fn disconnect_device(&self, device_type: &str, device_id: &str) -> anyhow::Result<()>;
47}
48
49/// No-op implementation used when the host hasn't registered a device-
50/// control backend. Keeps the WS frames from crashing a stripped-down
51/// build that doesn't supervise BLE devices.
52pub struct NoopDeviceControl;
53
54#[async_trait]
55impl DeviceControlHook for NoopDeviceControl {
56 async fn display_glyph(
57 &self,
58 device_type: &str,
59 device_id: &str,
60 _pattern: &str,
61 _brightness: Option<f32>,
62 _timeout_ms: Option<u32>,
63 _transition: Option<&str>,
64 ) -> anyhow::Result<()> {
65 tracing::warn!(
66 device_type,
67 device_id,
68 "DisplayGlyph received but no device-control hook is registered"
69 );
70 Ok(())
71 }
72
73 async fn connect_device(&self, device_type: &str, device_id: &str) -> anyhow::Result<()> {
74 tracing::warn!(
75 device_type,
76 device_id,
77 "DeviceConnect received but no device-control hook is registered"
78 );
79 Ok(())
80 }
81
82 async fn disconnect_device(&self, device_type: &str, device_id: &str) -> anyhow::Result<()> {
83 tracing::warn!(
84 device_type,
85 device_id,
86 "DeviceDisconnect received but no device-control hook is registered"
87 );
88 Ok(())
89 }
90}