Skip to main content

mx_remote/types/
status.rs

1// Author: Lars Op den Kamp (lars@opdenkamp-it.nl)
2// Copyright (c) 2026 Op den Kamp IT Solutions
3
4//! Device and bay state.
5
6use core::fmt;
7
8use crate::wire::{BayStatus, BayUid, FirmwareType, MxrSignalType};
9
10/// The high-level state of a device or bay on the network.
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub enum DeviceStatus {
13    /// Reachable and reporting.
14    #[default]
15    Online,
16    /// Has stopped answering.
17    Offline,
18    /// Announced a reboot.
19    Rebooting,
20    /// Still coming up.
21    Booting,
22    /// Present but not participating.
23    Inactive,
24}
25
26impl fmt::Display for DeviceStatus {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        f.write_str(match self {
29            Self::Online => "Online",
30            Self::Offline => "Offline",
31            Self::Rebooting => "Rebooting",
32            Self::Booting => "Booting",
33            Self::Inactive => "Inactive",
34        })
35    }
36}
37
38/// The CEC power state of a device connected to a bay.
39#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
40pub enum PowerStatus {
41    /// The device has not reported a power state.
42    #[default]
43    Unknown,
44    /// Powered on.
45    On,
46    /// Powered off.
47    Off,
48}
49
50impl fmt::Display for PowerStatus {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        f.write_str(match self {
53            Self::On => "on",
54            Self::Off => "off",
55            Self::Unknown => "unknown",
56        })
57    }
58}
59
60/// The connect / signal-detect state reported for a bay.
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
62pub enum ConnectStatus {
63    /// The device has not reported a connect state.
64    #[default]
65    Unknown,
66    /// Something is attached.
67    Connected,
68    /// Nothing is attached.
69    Disconnected,
70}
71
72impl fmt::Display for ConnectStatus {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.write_str(match self {
75            Self::Connected => "connected",
76            Self::Disconnected => "disconnected",
77            Self::Unknown => "unknown",
78        })
79    }
80}
81
82/// The visibility state of a bay.
83#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
84pub enum HiddenStatus {
85    /// The device has not reported a visibility.
86    #[default]
87    Unknown,
88    /// Hidden from the user interface.
89    Hidden,
90    /// Shown in the user interface.
91    Visible,
92}
93
94impl fmt::Display for HiddenStatus {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        f.write_str(match self {
97            Self::Hidden => "hidden",
98            Self::Visible => "visible",
99            Self::Unknown => "unknown",
100        })
101    }
102}
103
104/// The per-channel mute bitfield: bit 0 is left, bit 1 is right.
105#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
106pub struct MuteStatus(u8);
107
108impl MuteStatus {
109    /// Wraps the raw wire byte.
110    pub const fn from_wire(value: u8) -> Self {
111        Self(value)
112    }
113
114    /// Returns the raw wire byte.
115    pub const fn to_wire(self) -> u8 {
116        self.0
117    }
118
119    /// Whether the left channel is muted.
120    pub const fn left(self) -> bool {
121        self.0 & (1 << 0) != 0
122    }
123
124    /// Whether the right channel is muted.
125    pub const fn right(self) -> bool {
126        self.0 & (1 << 1) != 0
127    }
128
129    /// Whether either channel is muted.
130    pub const fn muted(self) -> bool {
131        self.0 != 0
132    }
133}
134
135/// The volume and mute state of a bay.
136///
137/// A `None` field is one the device did not report, which is distinct from a
138/// reported zero.
139#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
140pub struct VolumeMuteStatus {
141    /// Left channel volume, as a percentage.
142    pub volume_left: Option<u8>,
143    /// Right channel volume, as a percentage.
144    pub volume_right: Option<u8>,
145    /// Whether the left channel is muted.
146    pub muted_left: Option<bool>,
147    /// Whether the right channel is muted.
148    pub muted_right: Option<bool>,
149}
150
151impl VolumeMuteStatus {
152    /// The combined left/right volume percentage, or zero when neither channel
153    /// reported one.
154    pub fn volume(&self) -> u8 {
155        match (self.volume_left, self.volume_right) {
156            (Some(l), Some(r)) => ((u16::from(l) + u16::from(r)) / 2) as u8,
157            (Some(l), None) => l,
158            (None, Some(r)) => r,
159            (None, None) => 0,
160        }
161    }
162
163    /// The combined mute state, or `None` when neither channel reported one.
164    pub fn muted(&self) -> Option<bool> {
165        match (self.muted_left, self.muted_right) {
166            (None, None) => None,
167            (l, r) => Some(l.unwrap_or(false) || r.unwrap_or(false)),
168        }
169    }
170
171    /// Encodes the 3-byte `[volume_left, volume_right, muted]` field. A channel
172    /// that reported no volume is sent the combined one.
173    pub(crate) fn wire(&self) -> [u8; 3] {
174        [
175            self.volume_left.unwrap_or_else(|| self.volume()),
176            self.volume_right.unwrap_or_else(|| self.volume()),
177            self.muted_value(),
178        ]
179    }
180
181    /// Encodes the mute field as `MXR_AUDIO_MUTE_*`: a per-channel bitmask.
182    fn muted_value(&self) -> u8 {
183        if self.muted() != Some(true) {
184            return 0;
185        }
186        match (
187            self.muted_left.unwrap_or(false),
188            self.muted_right.unwrap_or(false),
189        ) {
190            (true, true) => 3,
191            (true, false) => 1,
192            _ => 2,
193        }
194    }
195}
196
197/// What a bay signal status report carries beyond the signal-detected flag and
198/// the human-readable signal type.
199#[derive(Clone, Copy, Debug, Default, PartialEq)]
200pub struct BaySignalDetails {
201    /// Frame rate in Hz, already corrected for a 1000/1001 clock.
202    pub frame_rate: f64,
203    /// TMDS clock rate in Hz.
204    pub tmds_clock: u32,
205    /// The bay status word from the report's bay block.
206    pub status: BayStatus,
207    /// The signal type the bay is scaling to.
208    pub scaling: MxrSignalType,
209    /// Video clock rate in Hz.
210    pub clock_rate: u32,
211}
212
213/// A firmware component reported by a device.
214#[derive(Clone, Debug, Default, PartialEq, Eq)]
215pub struct FirmwareVersion {
216    /// Which component this describes.
217    pub firmware_type: FirmwareType,
218    /// Build timestamp, in seconds since the Unix epoch.
219    pub timestamp: u32,
220    /// Human-readable version string.
221    pub version: String,
222    /// Source revision hash.
223    pub hash: u32,
224}
225
226impl fmt::Display for FirmwareVersion {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        write!(
229            f,
230            "firmware {} version {} hash {}",
231            self.firmware_type, self.version, self.hash
232        )
233    }
234}
235
236/// Whether an output bay mirrors another device's output, and which one.
237///
238/// The default is "not mirroring".
239#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
240pub struct BayMirrorStatus {
241    /// The bay being mirrored, or `None` when this bay mirrors nothing.
242    pub target: Option<BayUid>,
243}
244
245impl BayMirrorStatus {
246    /// Reports whether this bay mirrors another.
247    pub const fn is_mirroring(&self) -> bool {
248        self.target.is_some()
249    }
250}
251
252/// One device in a topology report.
253#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
254pub struct TopologyEntry {
255    /// The device this entry describes.
256    pub uid: crate::wire::DeviceUid,
257    /// Bitmask of the devices it is connected to.
258    pub mask: u32,
259}
260
261/// The audio return channel a bay is carrying.
262#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
263pub enum ArcStatus {
264    /// No audio is being returned.
265    #[default]
266    Inactive,
267    /// Returned over HDMI.
268    Hdmi,
269    /// Returned over optical.
270    Optical,
271    /// Returned over analogue.
272    Analog,
273}
274
275impl fmt::Display for ArcStatus {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        f.write_str(match self {
278            Self::Inactive => "Inactive",
279            Self::Hdmi => "HDMI",
280            Self::Optical => "optical",
281            Self::Analog => "analog",
282        })
283    }
284}