Skip to main content

huesmith_core/light/
mod.rs

1pub mod command;
2pub mod state;
3
4/// Trait for any LED output backend.
5/// Implementations handle the actual hardware (GPIO PWM, WS2812B, etc.).
6///
7/// # Call contract
8///
9/// [`state::LightState`] drives implementations under these rules:
10///
11/// - `set_on(false)` must drive the output dark immediately.
12/// - `set_on(true)` must restore the most recently set color/brightness on its
13///   own. The identify/effect blink calls *only* `set_on` in alternation, so an
14///   implementation that treats `set_on(true)` as a bare flag renders identify
15///   invisible.
16/// - Hardware errors are not propagated: the state machine has no recovery
17///   action for a failed LED write, so implementations log and continue.
18pub trait LightOutput: Send {
19    /// Turn the light on or off. On `true`, re-apply the last output state.
20    fn set_on(&mut self, on: bool);
21
22    /// Set brightness level (0-254, where 254 is full brightness).
23    fn set_brightness(&mut self, level: u8);
24
25    /// Set color using CIE 1931 XY coordinates (0-65535 range for each axis).
26    ///
27    /// Defaults to a no-op. [`state::LightState`] never calls this — it converts
28    /// XY to RGB and calls [`set_rgb`](LightOutput::set_rgb) instead — so
29    /// override it only for a backend you drive with raw XY yourself.
30    fn set_color_xy(&mut self, _x: u16, _y: u16) {}
31
32    /// Set color temperature in mireds (153 = 6500K, 500 = 2000K).
33    ///
34    /// Defaults to a no-op, so an on/off or plain-dimmable backend with no
35    /// tunable white needs to implement only [`set_on`](LightOutput::set_on) and
36    /// [`set_brightness`](LightOutput::set_brightness).
37    fn set_color_temp(&mut self, _mireds: u16) {}
38
39    /// Set color using direct RGB values.
40    ///
41    /// Required — the state machine renders the XY and hue/saturation color
42    /// modes (and dimmed color temperature) through this method, so a silently
43    /// inherited no-op would mean a light that pairs, powers, and dims but
44    /// never changes color. Backends with no color channel implement it as a
45    /// brightness mapping (see `SimpleLed`'s BT.709 luminance) or an explicit
46    /// empty body — the choice must be visible in the impl, not an invisible
47    /// default.
48    fn set_rgb(&mut self, r: u8, g: u8, b: u8);
49
50    /// Observe the full raw light state after every render pass.
51    ///
52    /// Defaults to a no-op; the built-in backends ignore it. **Pick one lane:**
53    /// simple hardware implements the `set_*` methods and ignores this hook;
54    /// an effects/full-control backend treats this hook as its single source
55    /// of truth and leaves the `set_*` bodies empty — handling both would
56    /// double-apply every change.
57    ///
58    /// The [`state::LightSnapshot`] carries every raw ZCL value with no
59    /// `Option`s: `on`, `brightness` (logical target, 0-254),
60    /// `rendered_brightness` (what is being driven *right now* — ramps during
61    /// fades; key visual effects off this one), `color_mode`,
62    /// `hue`/`saturation` (0-254), `enhanced_hue` (0-65535),
63    /// `color_x`/`color_y` (0-65535 CIE), `color_temp_mireds` (153-500).
64    /// During a smooth transition it fires on every animation frame
65    /// (~20-33 ms).
66    ///
67    /// Note: the identify blink signals through
68    /// [`set_on`](LightOutput::set_on) alone and does not re-render.
69    fn state_update(&mut self, _state: &state::LightSnapshot) {}
70}