A nightstand noise generator based on M5Stack Atom Echo and integrating with Home Assistant
1//! Type-level "wires" between the firmware modules.
2//!
3//! These enums define the shape of the cross-task message passing. Producers
4//! and consumers are independent; adding a new producer (e.g., MQTT in a
5//! later milestone) is a matter of adding another `Sender<AudioCommand>`,
6//! not changing any existing module.
7
8/// What the button task observes and reports to the coordinator.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ButtonEvent {
11 /// A single short press (released before 2s, no follow-up press within 400ms).
12 Short,
13 /// Held for 2s or more.
14 Long,
15 /// Two short presses within 400ms of each other.
16 Double,
17}
18
19impl ButtonEvent {
20 /// The wire-format string used in MQTT button event payloads.
21 pub fn as_event_type(self) -> &'static str {
22 match self {
23 Self::Short => "short",
24 Self::Long => "long",
25 Self::Double => "double",
26 }
27 }
28}
29
30/// What the coordinator (or, later, an MQTT task) tells the audio task to do.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum AudioCommand {
33 /// Start playing white noise at the current volume.
34 Play,
35 /// Stop playing.
36 Stop,
37 /// Toggle play state.
38 Toggle,
39 /// Advance the volume yo-yo by one step in the current direction.
40 CycleVolume,
41 /// Set volume directly to a specific preset index (0..VOLUME_PRESETS.len()).
42 /// Used when HA sends a numeric volume; we snap to the nearest preset.
43 SetVolumeIndex(u8),
44}
45
46/// Audio playback state, sent from the audio task to the LED task.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum AudioStatus {
49 Idle,
50 Playing,
51}
52
53/// Network connection state, sent from the network task to the LED task.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum NetStatus {
56 /// WiFi or MQTT not yet up — initial state and during reconnect attempts.
57 Connecting,
58 /// WiFi + MQTT both connected.
59 Online,
60 /// Was online, lost connection, or never connected and giving up for now.
61 Offline,
62}
63
64/// What the audio / network tasks tell the LED task to display.
65///
66/// The LED task tracks the most recent `Audio(_)` and `Net(_)` separately and
67/// renders the combined color from a 2-axis lookup. `Updating` and `Error`
68/// override both; `PressFlash` is an overlay that brightens whatever is
69/// currently shown.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum LedSignal {
72 /// Audio task reporting current playback state.
73 Audio(AudioStatus),
74 /// Network task reporting current connection state.
75 Net(NetStatus),
76 /// Brief brighter flash on top of whatever's currently being shown.
77 PressFlash,
78 /// OTA download in progress — overrides everything else with a magenta
79 /// pulse so it's visually obvious the device is mid-update.
80 Updating,
81 /// OTA download finished (either success or failure). Clears the
82 /// `Updating` override so the LED falls back to the audio/net axes.
83 /// On success the device reboots immediately, so this primarily exists
84 /// for the failure path (so the LED doesn't stay stuck magenta).
85 UpdateDone,
86 /// Something's broken — slow red blink. Reserved for unrecoverable
87 /// failures (I2S init, etc.); network outages are just `Net(Offline)`.
88 Error,
89}
90
91/// A snapshot of the audio task's state — what gets published to the
92/// `nightstand/<mac>/state` MQTT topic.
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub struct StateSnapshot {
95 pub playing: bool,
96 pub volume_pct: u8,
97}
98
99/// What the coordinator and audio task send to the network task for outbound
100/// publication. The network task is the gatekeeper: when offline, button
101/// events for `Short` are translated into a local `AudioCommand::Toggle`;
102/// state snapshots are cached for republish on reconnect.
103#[derive(Debug, Clone, Copy)]
104pub enum OutboundEvent {
105 Button(ButtonEvent),
106 State(StateSnapshot),
107}