Skip to main content

mx_remote/runtime/
info.rs

1// Author: Lars Op den Kamp (lars@opdenkamp-it.nl)
2// Copyright (c) 2026 Op den Kamp IT Solutions
3
4//! Snapshots of a device and of a bay.
5//!
6//! State lives behind a lock and is read by the receive thread, so a caller is
7//! handed a copy rather than a borrow of it. The copy also matches how the C
8//! ABI reads state: a uid in, a struct filled out.
9
10use std::net::Ipv4Addr;
11use std::time::Instant;
12
13use crate::state::{Bay, Device, State};
14use crate::types::{
15    AmpZoneSettings, ArcStatus, BayMirrorStatus, BaySignalDetails, DeviceStatus, PowerStatus,
16    VolumeMuteStatus,
17};
18use crate::wire::{
19    BayFeatures, BayUid, DeviceFeature, DeviceUid, EdidProfile, MxrSignalType, RcType,
20};
21
22/// What a device is, and what it is doing.
23#[derive(Clone, Debug, PartialEq, Eq)]
24#[non_exhaustive]
25pub struct DeviceInfo {
26    /// The device's unique identifier.
27    pub uid: DeviceUid,
28    /// The name the device advertises, or `Unknown` before it has said.
29    pub name: String,
30    /// Serial number, or `Unknown` before the device has said.
31    pub serial: String,
32    /// A friendly model name derived from the advertised name and the bays the
33    /// device reports.
34    pub model: String,
35    /// Firmware version string from the hello frame.
36    pub version: String,
37    /// The highest protocol version the device can decode, or zero before it
38    /// has said.
39    pub supported_protocol: u16,
40    /// What the device says it can do.
41    pub features: DeviceFeature,
42    /// The address the device was last heard from.
43    pub address: Option<Ipv4Addr>,
44    /// Online, offline, booting or rebooting.
45    pub status: DeviceStatus,
46    /// Whether the device has been heard from recently enough to count as
47    /// present.
48    pub online: bool,
49    /// Whether every part of the device's configuration has arrived.
50    pub configuration_complete: bool,
51    /// Whether the device's firmware initialises the configuration it
52    /// broadcasts.
53    ///
54    /// Firmware without it builds some frames over uninitialised stack, so
55    /// those fields carry noise rather than values: the scaling flags and,
56    /// behind a spuriously set valid bit, the scaling mode and refresh; bay 0's
57    /// addresses in the V2IP sources frame; and the padding beside the
58    /// remote-control target.
59    pub config_initialised: bool,
60    /// Temperatures the device reports, in degrees Celsius, in its own order.
61    pub temperatures: Vec<u8>,
62    /// The mesh master this device follows, or the zero uid when it is in no
63    /// mesh.
64    pub mesh_master: DeviceUid,
65    /// How many HDBaseT outputs this model has.
66    pub hdbt_outputs: u8,
67    /// Whether installation was marked complete, before the device has said.
68    pub setup_done: Option<bool>,
69    /// The installer identifier the device carries, if it has reported one.
70    pub installer_id: Option<u16>,
71    /// A status code and message the device reports about itself.
72    pub system_status: Option<(u16, String)>,
73    /// Every bay on the device, in port order.
74    pub bays: Vec<BayUid>,
75}
76
77impl DeviceInfo {
78    pub(crate) fn of(device: &Device, now: Instant) -> Self {
79        Self {
80            uid: device.uid,
81            name: device.name().to_owned(),
82            serial: device.serial().to_owned(),
83            model: device.model_name().to_owned(),
84            version: device.hello.version.clone(),
85            supported_protocol: device.hello.supported_protocol,
86            features: device.hello.features,
87            address: device.hello.address,
88            status: device.status(now),
89            online: device.is_online(now),
90            configuration_complete: device.configuration_complete(),
91            config_initialised: device.config_initialised(),
92            temperatures: device.temperatures.clone(),
93            mesh_master: device.mesh_master,
94            hdbt_outputs: device.hdbt_outputs(),
95            setup_done: device.setup_done,
96            installer_id: device.installer_id,
97            system_status: device.sys_status.clone(),
98            bays: device.bays.values().map(Bay::uid).collect(),
99        }
100    }
101}
102
103/// What a bay is, and what is connected to it.
104///
105/// A status this bay has never reported is `None`, which is not the same as
106/// the status being off: a device reports only what it has.
107#[derive(Clone, Debug, PartialEq)]
108#[non_exhaustive]
109pub struct BayInfo {
110    /// How the bay is addressed: its device and its port.
111    pub uid: BayUid,
112    /// The name the device gives the port, such as `Output 1`.
113    pub port_name: String,
114    /// The name the installer gave the bay, falling back to the port name.
115    pub user_name: String,
116    /// The bay number the device's own API and topology use.
117    pub bay_num: u8,
118    /// What the bay is wired for.
119    pub features: BayFeatures,
120    /// Whether the bay takes a signal in.
121    pub is_input: bool,
122    /// Whether the bay puts a signal out.
123    pub is_output: bool,
124    /// Whether the bay carries audio and no video.
125    pub is_audio: bool,
126    /// Whether the bay can decode Dolby.
127    pub has_dolby: bool,
128    /// Whether the bay is on this device rather than reached through the mesh.
129    pub is_local: bool,
130    /// The bay routed to this one for video.
131    pub video_source: Option<BayUid>,
132    /// The bay routed to this one for audio, which follows the video source
133    /// until the bay is told otherwise.
134    pub audio_source: Option<BayUid>,
135    /// Power state of what is connected.
136    pub power_status: Option<PowerStatus>,
137    /// Whether the bay is hidden from the installation's user interface.
138    pub hidden: Option<bool>,
139    /// Whether the device reports the bay as faulty.
140    pub faulty: Option<bool>,
141    /// Whether the bay is delivering power over the link.
142    pub poe_powered: Option<bool>,
143    /// Whether an HDBaseT link is up.
144    pub hdbt_connected: Option<bool>,
145    /// Whether a signal is present.
146    pub signal_detected: Option<bool>,
147    /// Whether hot-plug detect is asserted.
148    pub hpd_detected: Option<bool>,
149    /// Whether a CEC device answered.
150    pub cec_detected: Option<bool>,
151    /// Whether the bay's encoder is switched off.
152    pub encoder_disabled: Option<bool>,
153    /// Whether the bay's decoder is switched off.
154    pub decoder_disabled: Option<bool>,
155    /// The signal as the device describes it, such as `1080p60 444 8`.
156    pub signal_type: Option<String>,
157    /// The signal as the device measures it.
158    pub signal_details: Option<BaySignalDetails>,
159    /// The signal format the device reports for the bay.
160    pub signal_mode: MxrSignalType,
161    /// Whether audio return is active, and over which connector.
162    pub arc: ArcStatus,
163    /// Volume and mute, for a bay that has them.
164    ///
165    /// A bay with no volume control of its own reads its
166    /// [`linked_bay`](BayInfo::linked_bay)'s, because a link to an amplifier
167    /// zone is how the mesh says that zone is this bay's volume.
168    pub volume: Option<VolumeMuteStatus>,
169    /// The kind of remote control attached.
170    pub rc_type: Option<RcType>,
171    /// The EDID profile the bay presents.
172    pub edid_profile: Option<EdidProfile>,
173    /// Which bay this one mirrors, if any.
174    pub mirror: BayMirrorStatus,
175    /// The audio endpoint this bay feeds, on a device that has them.
176    pub audio_endpoint: Option<u8>,
177    /// Amplifier settings, on an amplifier zone.
178    pub amp_settings: Option<AmpZoneSettings>,
179    /// Devices whose signals this bay refuses.
180    pub filtered: Vec<DeviceUid>,
181    /// The bay on another device this one is linked to, once that bay has been
182    /// discovered.
183    ///
184    /// A link is mesh configuration rather than a route: it names the bay
185    /// elsewhere that belongs to this one, such as the amplifier zone carrying
186    /// a OneIP output's volume. [`BayInfo::volume`] is already read through it.
187    pub linked_bay: Option<BayUid>,
188    /// The source device a V2IP bay maps to, or the zero uid.
189    pub v2ip_uid: DeviceUid,
190}
191
192impl BayInfo {
193    pub(crate) fn of(state: &State, bay: &Bay) -> Self {
194        Self {
195            uid: bay.uid(),
196            port_name: bay.port_name.clone(),
197            user_name: bay.user_name().to_owned(),
198            bay_num: bay.bay_num(),
199            features: bay.features,
200            is_input: bay.is_input(),
201            is_output: bay.is_output(),
202            is_audio: bay.is_audio(),
203            has_dolby: bay.has_dolby(),
204            is_local: bay.is_local(),
205            video_source: bay.video_source,
206            audio_source: bay.effective_audio_source(),
207            power_status: bay.power_status,
208            hidden: bay.hidden,
209            faulty: bay.faulty,
210            poe_powered: bay.poe_powered,
211            hdbt_connected: bay.hdbt_connected,
212            signal_detected: bay.signal_detected,
213            hpd_detected: bay.hpd_detected,
214            cec_detected: bay.cec_detected,
215            encoder_disabled: bay.encoder_disabled,
216            decoder_disabled: bay.decoder_disabled,
217            signal_type: bay.signal_type.clone(),
218            signal_details: bay.signal_details,
219            signal_mode: bay.signal_mode,
220            arc: bay.arc,
221            volume: state
222                .bay(state.volume_bay(bay.uid()))
223                .and_then(|b| b.audio_volume),
224            rc_type: bay.rc_type,
225            edid_profile: bay.edid_profile,
226            mirror: bay.mirror,
227            audio_endpoint: bay.audio_endpoint,
228            amp_settings: bay.amp_settings,
229            filtered: bay.filtered.clone(),
230            linked_bay: state.linked_bay(bay.uid()),
231            v2ip_uid: bay.v2ip_uid,
232        }
233    }
234}