Skip to main content

mx_remote/wire/
enums.rs

1// Author: Lars Op den Kamp (lars@opdenkamp-it.nl)
2// Copyright (c) 2026 Op den Kamp IT Solutions
3
4//! Wire enumerations and bitmasks.
5//!
6//! Each type is a newtype over the integer that travels on the wire, with
7//! named constants rather than a closed set of variants. A value this library
8//! has no name for reaches the caller as it arrived: zero is a valid value for
9//! most of these, so a confidently wrong reading is worse than an unrecognised
10//! one.
11
12use core::fmt;
13use core::ops::{BitAnd, BitOr, BitOrAssign};
14
15/// Declares a bitmask newtype with the given named bit constants.
16///
17/// The representation defaults to `u32`; give it explicitly as `Name: u64` for
18/// a mask whose wire field is wider.
19macro_rules! bitmask {
20    (
21        $(#[$meta:meta])*
22        $name:ident { $( $(#[$cmeta:meta])* $cname:ident = $value:expr; )* }
23    ) => {
24        bitmask! {
25            $(#[$meta])*
26            $name: u32 { $( $(#[$cmeta])* $cname = $value; )* }
27        }
28    };
29    (
30        $(#[$meta:meta])*
31        $name:ident: $repr:ty { $( $(#[$cmeta:meta])* $cname:ident = $value:expr; )* }
32    ) => {
33        $(#[$meta])*
34        #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
35        pub struct $name($repr);
36
37        impl $name {
38            /// No bits set.
39            pub const NONE: Self = Self(0);
40
41            $( $(#[$cmeta])* pub const $cname: Self = Self($value); )*
42
43            /// Wraps a raw wire value, including bits this library has no name for.
44            pub const fn from_bits(bits: $repr) -> Self {
45                Self(bits)
46            }
47
48            /// Returns the raw wire value.
49            pub const fn bits(self) -> $repr {
50                self.0
51            }
52
53            /// Reports whether every bit in `other` is set.
54            pub const fn has(self, other: Self) -> bool {
55                self.0 & other.0 == other.0
56            }
57
58            /// Reports whether no bit is set.
59            pub const fn is_empty(self) -> bool {
60                self.0 == 0
61            }
62        }
63
64        impl BitOr for $name {
65            type Output = Self;
66            fn bitor(self, rhs: Self) -> Self {
67                Self(self.0 | rhs.0)
68            }
69        }
70
71        impl BitOrAssign for $name {
72            fn bitor_assign(&mut self, rhs: Self) {
73                self.0 |= rhs.0;
74            }
75        }
76
77        impl BitAnd for $name {
78            type Output = Self;
79            fn bitand(self, rhs: Self) -> Self {
80                Self(self.0 & rhs.0)
81            }
82        }
83
84        impl fmt::Display for $name {
85            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86                // Two hex digits per byte of the wire field, plus "0x".
87                write!(
88                    f,
89                    "{:#0width$x}",
90                    self.0,
91                    width = core::mem::size_of::<$repr>() * 2 + 2
92                )
93            }
94        }
95    };
96}
97
98/// Declares an enumeration newtype over `$repr` with the given named constants.
99macro_rules! wire_enum {
100    (
101        $(#[$meta:meta])*
102        $name:ident: $repr:ty { $( $(#[$cmeta:meta])* $cname:ident = $value:expr; )* }
103    ) => {
104        $(#[$meta])*
105        #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
106        pub struct $name($repr);
107
108        impl $name {
109            $( $(#[$cmeta])* pub const $cname: Self = Self($value); )*
110
111            /// Wraps a raw wire value, including one this library has no name for.
112            pub const fn from_wire(value: $repr) -> Self {
113                Self(value)
114            }
115
116            /// Returns the raw wire value.
117            pub const fn to_wire(self) -> $repr {
118                self.0
119            }
120        }
121    };
122}
123
124bitmask! {
125    /// Capabilities a device reports in its hello frame.
126    DeviceFeature {
127        /// Receives infrared.
128        IR_RX = 1 << 0;
129        /// Transmits infrared.
130        IR_TX = 1 << 1;
131        /// Speaks CEC.
132        CEC = 1 << 2;
133        /// Acts as a V2IP stream source.
134        V2IP_SOURCE = 1 << 3;
135        /// Acts as a V2IP stream sink.
136        V2IP_SINK = 1 << 4;
137        /// Routes video.
138        VIDEO_ROUTING = 1 << 5;
139        /// Routes audio.
140        AUDIO_ROUTING = 1 << 6;
141        /// Controls volume.
142        VOLUME_CONTROL = 1 << 7;
143        /// Supports audio return.
144        AUDIO_RETURN = 1 << 8;
145        /// Passes remote-control commands through.
146        REMOTE_CONTROL = 1 << 9;
147        /// Installer setup has been completed.
148        SETUP_COMPLETED = 1 << 10;
149        /// Is the master of its mesh.
150        MESH_MASTER = 1 << 11;
151        /// Has a notification pending.
152        STATUS_NOTIFY = 1 << 12;
153        /// Has a warning pending.
154        STATUS_WARNING = 1 << 13;
155        /// Has an error pending.
156        STATUS_ERROR = 1 << 14;
157        /// Is about to reboot.
158        STATUS_REBOOT = 1 << 15;
159        /// Is a member of a mesh.
160        MESH_MEMBER = 1 << 16;
161        /// Is an audio amplifier.
162        AUDIO_AMPLIFIER = 1 << 17;
163        /// Is still booting.
164        BOOTING = 1 << 18;
165        /// Is a management client rather than a device.
166        MANAGER = 1 << 19;
167        /// Is in power-save mode.
168        STATUS_POWER_SAVE = 1 << 20;
169        /// Supports meshing.
170        MESH = 1 << 21;
171        /// Is a multiviewer.
172        MULTIVIEWER = 1 << 22;
173        /// Has crashed since it last booted.
174        STATUS_CRASHED = 1 << 23;
175        /// Supports video walls.
176        VIDEO_WALL = 1 << 24;
177        /// Initialises the configuration it broadcasts.
178        ///
179        /// Firmware without this bit sends a device configuration built over
180        /// uninitialised memory, so fields it did not mean to write carry junk.
181        CONFIG_INITIALISED = 1 << 25;
182        /// Receives one or more peers only by broadcast, because its multicast
183        /// receive path is faulty.
184        STATUS_MCAST_FAULT = 1 << 26;
185        /// Powers its video processor down in power save. A device without it
186        /// refuses to enter power save.
187        POWER_SAVE = 1 << 27;
188        /// Tags its uplink by the VLAN configuration it reports, and takes one
189        /// written to it.
190        VLAN = 1 << 28;
191        /// Flips on every reboot, so a peer that sees it change knows the device restarted.
192        BOOT_BIT = 1 << 31;
193    }
194}
195
196bitmask! {
197    /// What a V2IP device's video processor supports, as the device reports it
198    /// in its configuration.
199    ///
200    /// Read-only, and a device's own: it fills the field in only on the frame
201    /// describing itself, and leaves it zero on one it sends to configure
202    /// another device. There is no write path.
203    ///
204    /// Bits are assigned by the video processor and only ever appended, so a
205    /// bit this library has no name for is a later capability rather than an
206    /// error. A device reports no features at all until its processor answers,
207    /// and an older processor answers with none of the optional commands, so an
208    /// empty mask is never reported as a capability set - see
209    /// [`crate::Remote::v2ip_features`], which reports it as unknown instead.
210    V2ipFpgaFeature: u64 {
211        /// Applies a DSCP marking to the streams it sources.
212        SOURCE_DSCP = 1 << 0;
213        /// Reports the audio format arriving at its sink.
214        SINK_AUDIO_FORMAT = 1 << 1;
215        /// Places a tiling window on its sink.
216        SINK_TILING_WINDOW = 1 << 2;
217        /// Reports the state of its sink's overlay.
218        SINK_OVERLAY_STATE = 1 << 3;
219        /// Reports its sink's state.
220        SINK_STATE = 1 << 4;
221        /// Reports information about the stream its sink receives.
222        SINK_STREAM_INFO = 1 << 5;
223        /// Draws a test pattern, tone and lip-sync flash on its sink's output.
224        SINK_TEST_PATTERN = 1 << 8;
225    }
226}
227
228bitmask! {
229    /// The device settings a V2IP configuration can carry, each on its own bit.
230    ///
231    /// The same bits serve as the settings a frame carries and as the values
232    /// of the on/off ones among them. `IR_PROFILE` to `POWER_SAVE_SCHEDULE`
233    /// carry a value elsewhere in the block and have no on/off value of their
234    /// own.
235    ///
236    /// A device reports every setting it has, so one it never reports is one
237    /// it does not have.
238    V2ipDeviceSetting {
239        /// Disables the decoder while the display is off.
240        SINK_CHECK_POWER = 1 << 0;
241        /// Disables the HDMI output while there is no signal.
242        SINK_OFF_NO_SIGNAL = 1 << 1;
243        /// Sends infrared modulated.
244        IR_TX_MODULATED = 1 << 2;
245        /// Lights the status LED.
246        STATUS_LED = 1 << 3;
247        /// Lights the network port LEDs.
248        NETWORK_LED = 1 << 4;
249        /// Runs the fan in quiet mode.
250        FAN_QUIET = 1 << 5;
251        /// Accepts CEC combo keys.
252        CEC_COMBO_KEYS = 1 << 6;
253        /// Accepts CEC combo keys for the device's own input.
254        CEC_COMBO_INPUT = 1 << 7;
255        /// The infrared profile of the device's global infrared port.
256        IR_PROFILE = 1 << 8;
257        /// The infrared profile of the output's infrared port.
258        IR_PROFILE_SINK = 1 << 9;
259        /// The infrared profiles stored on the device. Reported by the device
260        /// itself and never written.
261        IR_PROFILES = 1 << 10;
262        /// The minutes a device stays idle before it powers down by itself.
263        AUTO_POWER_SAVE = 1 << 11;
264        /// The daily windows in which the device powers down.
265        POWER_SAVE_SCHEDULE = 1 << 12;
266        /// The device's clock has been set, on/off. Reported by the device
267        /// itself and never written.
268        CLOCK_SET = 1 << 13;
269        /// The PTP mode the device runs.
270        PTP_MODE = 1 << 14;
271        /// The PTP domain the device runs in.
272        PTP_DOMAIN = 1 << 15;
273        /// The priority1 the device announces as a PTP grandmaster.
274        PTP_PRIORITY1 = 1 << 16;
275    }
276}
277
278wire_enum! {
279    /// How a V2IP device takes part in PTP.
280    V2ipPtpMode: u8 {
281        /// No PTP.
282        OFF = 0;
283        /// Follows a grandmaster, and never becomes one.
284        FOLLOWER = 1;
285        /// Follows a grandmaster, or becomes one when there is none.
286        AUTO = 2;
287    }
288}
289
290wire_enum! {
291    /// The test pattern a V2IP sink draws on its output.
292    V2ipTestPattern: u8 {
293        /// No pattern.
294        OFF = 0;
295        /// Colour bars.
296        BARS = 1;
297        /// One flat colour, the one the test card carries.
298        FLAT = 2;
299        /// A ramp.
300        RAMP = 3;
301        /// A grid.
302        GRID = 4;
303        /// A strip.
304        STRIP = 5;
305        /// A test card.
306        CARD = 6;
307    }
308}
309
310wire_enum! {
311    /// The test tone a V2IP sink plays on its output.
312    V2ipToneMode: u8 {
313        /// No tone.
314        OFF = 0;
315        /// A continuous tone.
316        CONTINUOUS = 1;
317        /// A channel ident.
318        IDENT = 2;
319        /// A line-up tone, which needs two channels or more.
320        LINEUP = 3;
321        /// A beep on each lip-sync mark.
322        BEEP = 4;
323    }
324}
325
326bitmask! {
327    /// What a V2IP sink reports about its test card.
328    V2ipTestcardFlag: u8 {
329        /// The sink can draw the test card.
330        SUPPORTED = 1 << 0;
331        /// The output shows the pattern.
332        SHOWING = 1 << 1;
333        /// The tone plays on the output.
334        PLAYING = 1 << 2;
335        /// A pattern change has yet to reach the video processor.
336        PATTERN_PENDING = 1 << 3;
337        /// A tone change has yet to reach the video processor.
338        TONE_PENDING = 1 << 4;
339        /// A lip-sync change has yet to reach the video processor.
340        SYNC_PENDING = 1 << 5;
341    }
342}
343
344bitmask! {
345    /// The flags of a unit's status report.
346    UnitFlag {
347        /// A changed setting takes effect after a reboot.
348        REBOOT_REQUIRED = 1 << 0;
349        /// A newer video processor image is available.
350        FPGA_UPGRADE = 1 << 1;
351        /// The IP configuration comes from DHCP.
352        DHCP = 1 << 2;
353        /// The IP configuration was changed through the mesh, and is reverted
354        /// unless the mesh controller confirms it in time.
355        NET_PENDING = 1 << 3;
356        /// The fan runs in quiet mode.
357        FAN_QUIET = 1 << 4;
358        /// Powered by an external supply rather than PoE.
359        EXTERNAL_PSU = 1 << 5;
360        /// The video processor runs with its IP configured. Without it the
361        /// encoder and decoder health carry nothing.
362        FPGA_CONFIGURED = 1 << 6;
363        /// The encoder is enabled.
364        ENCODER_ACTIVE = 1 << 7;
365        /// The decoder is enabled.
366        DECODER_ACTIVE = 1 << 8;
367        /// The decoder disables itself without a display.
368        AUTO_DISABLE = 1 << 9;
369        /// The decoder disabled itself without a display.
370        AUTO_DISABLE_ACTIVE = 1 << 10;
371        /// The HDMI output switches off without a signal.
372        OFF_NO_SIGNAL = 1 << 11;
373        /// The HDMI output switched off without a signal.
374        OFF_NO_SIGNAL_ACTIVE = 1 << 12;
375        /// Display power is checked before the decoder is enabled.
376        POWER_CHECK = 1 << 13;
377        /// The boot count is reported.
378        BOOT_COUNT = 1 << 14;
379        /// The fan speed is reported.
380        FAN_SPEED = 1 << 15;
381    }
382}
383
384wire_enum! {
385    /// What a unit command asks of its unit.
386    UnitCommandKind: u8 {
387        /// Clear the current system status.
388        CLEAR_STATUS = 0;
389        /// Upgrade the video processor to the latest image.
390        FPGA_UPGRADE = 1;
391        /// Change the IP configuration.
392        NET_CONFIG = 2;
393        /// Keep a pending IP configuration.
394        NET_CONFIRM = 3;
395    }
396}
397
398bitmask! {
399    /// The flags of a unit command.
400    UnitCommandFlag: u8 {
401        /// With [`UnitCommandKind::FPGA_UPGRADE`]: flash the image even if it
402        /// is running already.
403        FORCE = 1 << 0;
404        /// With [`UnitCommandKind::NET_CONFIG`]: use DHCP, ignoring the
405        /// addresses.
406        DHCP = 1 << 1;
407    }
408}
409
410bitmask! {
411    /// The flags of a V2IP device's PTP state.
412    V2ipPtpFlag: u8 {
413        /// The device runs PTP and the block holds its state. A block without
414        /// it holds nothing: the device has no PTP, or is in power save.
415        VALID = 1 << 0;
416        /// The device locked to a master since its PTP started, so its mesh
417        /// holds a time.
418        FOLLOWED = 1 << 1;
419    }
420}
421
422wire_enum! {
423    /// How far a V2IP device's PTP clock has locked to its master.
424    V2ipPtpLock: u8 {
425        /// Not locked.
426        UNLOCKED = 0;
427        /// Locking.
428        LOCKING = 1;
429        /// Locked.
430        LOCKED = 2;
431    }
432}
433
434bitmask! {
435    /// The flags of a V2IP device's VLAN configuration.
436    V2ipVlanFlag: u16 {
437        /// The block carries a configuration. A block without it carries
438        /// nothing, whatever its other bytes hold.
439        VALID = 1 << 0;
440        /// Untagged frames arriving on the uplink are dropped instead of
441        /// reaching the device.
442        TRUNK = 1 << 1;
443        /// Reported by the device: the configuration is applied, and reverted
444        /// unless the mesh controller confirms it. Never written.
445        PENDING = 1 << 2;
446        /// Sent by the mesh controller: a pending configuration it heard, which
447        /// the device then keeps.
448        CONFIRM = 1 << 3;
449        /// Reported by the device: it has an SFP port. Never written.
450        HAS_SFP = 1 << 4;
451    }
452}
453
454impl V2ipDeviceSetting {
455    /// The settings that are on or off, as opposed to carrying a value, and
456    /// that can be written.
457    pub const SWITCHES: Self = Self(0xFF);
458
459    /// The settings only a device reports about itself, which no write
460    /// carries.
461    pub const REPORTED_ONLY: Self = Self(Self::IR_PROFILES.0 | Self::CLOCK_SET.0);
462
463    /// The PTP settings.
464    pub const PTP_SETTINGS: Self =
465        Self(Self::PTP_MODE.0 | Self::PTP_DOMAIN.0 | Self::PTP_PRIORITY1.0);
466
467    /// These bits with every bit of `other` cleared.
468    pub(crate) const fn without(self, other: Self) -> Self {
469        Self(self.0 & !other.0)
470    }
471}
472
473bitmask! {
474    /// Capabilities of a single bay.
475    BayFeatures {
476        /// HDMI output.
477        HDMI_OUT = 1 << 0;
478        /// HDMI input.
479        HDMI_IN = 1 << 1;
480        /// Digital audio output.
481        AUDIO_DIG_OUT = 1 << 2;
482        /// Digital audio input.
483        AUDIO_DIG_IN = 1 << 3;
484        /// Analogue audio output.
485        AUDIO_ANA_OUT = 1 << 4;
486        /// Analogue audio input.
487        AUDIO_ANA_IN = 1 << 5;
488        /// Infrared input.
489        IR_IN = 1 << 6;
490        /// Infrared output.
491        IR_OUT = 1 << 7;
492        /// Amplified audio output.
493        AUDIO_AMP_OUT = 1 << 8;
494        /// Remote-control output.
495        RC_OUT = 1 << 9;
496        /// Remote-control input.
497        RC_IN = 1 << 10;
498        /// Dolby decoding.
499        DOLBY = 1 << 11;
500        /// Switches itself off when idle.
501        AUTO_OFF = 1 << 12;
502        /// Is a remote V2IP source.
503        V2IP_SOURCE_REMOTE = 1 << 13;
504        /// Is a remote V2IP sink.
505        V2IP_SINK_REMOTE = 1 << 14;
506        /// Is a local V2IP source.
507        V2IP_SOURCE_LOCAL = 1 << 15;
508        /// Is a local V2IP sink.
509        V2IP_SINK_LOCAL = 1 << 16;
510    }
511}
512
513bitmask! {
514    /// Live status flags of a single bay.
515    ///
516    /// Bits 16-19 and 22-23 are bit-fields rather than flags; read them with
517    /// [`BayStatus::rc_type`] and [`BayStatus::hdcp`].
518    BayStatus {
519        /// The bay reports a fault.
520        FAULT = 1 << 0;
521        /// The bay is hidden from the user interface.
522        HIDDEN = 1 << 1;
523        /// The bay has power.
524        POWERED = 1 << 2;
525        /// A signal is present.
526        SIGNAL_DETECTED = 1 << 3;
527        /// Hot-plug detect is asserted.
528        HPD_DETECTED = 1 << 4;
529        /// The signal is scrambled.
530        SIGNAL_SCRAMBLE = 1 << 5;
531        /// An HDBaseT link is up.
532        HDBT_CONNECTED = 1 << 6;
533        /// A CEC device answered.
534        CEC_DETECTED = 1 << 7;
535        /// The attached device was powered on.
536        POWERED_ON = 1 << 8;
537        /// The attached device was powered off.
538        POWERED_OFF = 1 << 9;
539        /// Audio return over HDMI is active.
540        AUDIO_ARC_HDMI = 1 << 10;
541        /// Audio return over optical is active.
542        AUDIO_ARC_OPTIC = 1 << 11;
543        /// Audio return over analogue is active.
544        AUDIO_ARC_ANALOG = 1 << 12;
545        /// The bay is offline.
546        OFFLINE = 1 << 13;
547        /// The V2IP decoder is disabled.
548        DECODER_DISABLE = 1 << 14;
549        /// The V2IP encoder is disabled.
550        ENCODER_DISABLE = 1 << 15;
551        /// CEC is switched off for this bay.
552        CEC_DISABLED = 1 << 20;
553        /// The V2IP encoder reports an error.
554        ENCODER_ERROR = 1 << 21;
555        /// The bay's name was generated - a default, or taken from CEC or the
556        /// EDID - rather than set by a user. A device that predates this bit
557        /// never sets it, so a clear bit does not prove a user set the name.
558        AUTO_NAME = 1 << 24;
559    }
560}
561
562impl BayStatus {
563    const RC_TYPE_SHIFT: u32 = 16;
564    const RC_TYPE_MASK: u32 = 0xF << Self::RC_TYPE_SHIFT;
565    const HDCP_SHIFT: u32 = 22;
566    const HDCP_MASK: u32 = 0x3 << Self::HDCP_SHIFT;
567
568    /// Extracts the remote-control type carried in bits 16-19.
569    pub const fn rc_type(self) -> RcType {
570        RcType(((self.0 & Self::RC_TYPE_MASK) >> Self::RC_TYPE_SHIFT) as u8)
571    }
572
573    /// Extracts the HDCP version carried in bits 22-23.
574    pub const fn hdcp(self) -> u8 {
575        ((self.0 & Self::HDCP_MASK) >> Self::HDCP_SHIFT) as u8
576    }
577}
578
579bitmask! {
580    /// Media carried by a virtual link.
581    LinkFeature {
582        /// Video over HDMI.
583        VIDEO_HDMI = 1 << 0;
584        /// Audio over optical.
585        AUDIO_OPTICAL = 1 << 1;
586        /// Audio over analogue.
587        AUDIO_ANALOG = 1 << 2;
588        /// Infrared.
589        IR = 1 << 3;
590        /// Remote control.
591        RC = 1 << 4;
592    }
593}
594
595wire_enum! {
596    /// A remote-control action.
597    RcAction: u16 {
598        /// Toggle power.
599        POWER_TOGGLE = 0;
600        /// Power on.
601        POWER_ON = 1;
602        /// Power off.
603        POWER_OFF = 2;
604        /// Volume down.
605        VOLUME_DOWN = 3;
606        /// Volume up.
607        VOLUME_UP = 4;
608        /// Toggle mute.
609        VOLUME_MUTE = 5;
610    }
611}
612
613wire_enum! {
614    /// A remote-control key code (CEC or IR).
615    RcKey: u16 {
616        /// Digit 0.
617        NUM0 = 0;
618        /// Digit 1.
619        NUM1 = 1;
620        /// Digit 2.
621        NUM2 = 2;
622        /// Digit 3.
623        NUM3 = 3;
624        /// Digit 4.
625        NUM4 = 4;
626        /// Digit 5.
627        NUM5 = 5;
628        /// Digit 6.
629        NUM6 = 6;
630        /// Digit 7.
631        NUM7 = 7;
632        /// Digit 8.
633        NUM8 = 8;
634        /// Digit 9.
635        NUM9 = 9;
636        /// Confirm the highlighted item.
637        SELECT = 10;
638        /// Go back one step.
639        BACK = 11;
640        /// Navigate up.
641        UP = 12;
642        /// Navigate down.
643        DOWN = 13;
644        /// Navigate left.
645        LEFT = 14;
646        /// Navigate right.
647        RIGHT = 15;
648        /// Open the main menu.
649        MENU = 16;
650        /// Open the content menu.
651        CONTENT_MENU = 17;
652        /// Next channel.
653        CHANNEL_UP = 18;
654        /// Previous channel.
655        CHANNEL_DOWN = 19;
656        /// Start playback.
657        PLAY = 20;
658        /// Pause playback.
659        PAUSE = 21;
660        /// Stop playback.
661        STOP = 22;
662        /// Start recording.
663        RECORD = 23;
664        /// Fast forward.
665        FAST_FORWARD = 24;
666        /// Rewind.
667        REWIND = 25;
668        /// Red colour key.
669        RED = 26;
670        /// Green colour key.
671        GREEN = 27;
672        /// Yellow colour key.
673        YELLOW = 28;
674        /// Blue colour key.
675        BLUE = 29;
676        /// Open help.
677        HELP = 30;
678        /// Show information.
679        INFORMATION = 31;
680        /// Open teletext.
681        TEXT = 32;
682        /// Open the programme guide.
683        GUIDE = 33;
684        /// Open video on demand.
685        VIDEO_ON_DEMAND = 34;
686        /// Return to the previous channel.
687        PREVIOUS_CHANNEL = 80;
688        /// Toggle 3D mode.
689        MODE_3D = 81;
690        /// Toggle subtitles.
691        SUBTITLE = 82;
692        /// Select an audio track.
693        SOUND_SELECT = 83;
694        /// Select an input.
695        INPUT_SELECT = 84;
696        /// Eject the medium.
697        EJECT = 85;
698        /// Next chapter.
699        NEXT_CHAPTER = 86;
700        /// Previous chapter.
701        PREV_CHAPTER = 87;
702        /// Open interactive services.
703        INTERACTIVE = 128;
704        /// Open search.
705        SEARCH = 129;
706        /// Sky home key.
707        SKY = 130;
708        /// Base of the range carrying a raw CEC user-control code.
709        CUSTOM_CEC = 1280;
710        /// Base of the range carrying a raw Sky key code.
711        CUSTOM_SKY = 2048;
712    }
713}
714
715wire_enum! {
716    /// The remote-control protocol of a connected sink or source.
717    RcType: u8 {
718        /// Infrared.
719        IR = 0;
720        /// HDMI CEC.
721        CEC = 1;
722        /// Sky UK over IP.
723        SKY_UK = 2;
724        /// TiVo.
725        TIVO = 3;
726        /// Kodi.
727        KODI = 4;
728        /// Dish.
729        DISH = 5;
730        /// DirecTV.
731        DIRECTV = 6;
732        /// Another MX Remote device.
733        MX_REMOTE = 7;
734    }
735}
736
737impl fmt::Display for RcType {
738    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
739        let name = match *self {
740            Self::IR => "IR",
741            Self::CEC => "CEC",
742            Self::SKY_UK => "Sky",
743            Self::TIVO => "TiVo",
744            Self::KODI => "Kodi",
745            Self::DISH => "Dish",
746            Self::DIRECTV => "DirecTV",
747            Self::MX_REMOTE => "MX-Remote",
748            _ => "Unknown",
749        };
750        f.write_str(name)
751    }
752}
753
754wire_enum! {
755    /// An EDID preset selectable on an HDMI input.
756    EdidProfile: u16 {
757        /// 1080p with stereo audio.
758        STEREO_1080P = 0;
759        /// A fixed EDID stored on the device.
760        FIXED = 1;
761        /// 4K.
762        UHD_4K = 2;
763        /// 1080p with 5.1 audio.
764        SURROUND51_1080P = 3;
765        /// 720p.
766        HD_720P = 4;
767        /// 1080p with 7.1 audio.
768        SURROUND71_1080P = 5;
769        /// 4K with 7.1 audio.
770        SURROUND71_4K = 6;
771        /// 4K HDR with stereo audio.
772        HDR_STEREO_4K = 7;
773        /// 4K HDR with 7.1 audio.
774        HDR_SURROUND71_4K = 8;
775        /// 4K HDR, audio to the AVR only.
776        HDR_AVR_ONLY_4K = 9;
777        /// The lowest common denominator of the connected sinks.
778        LOWEST_COMMON = 10;
779        /// The lowest common denominator of every sink, connected or not.
780        LOWEST_COMMON_ALL = 11;
781        /// 4K HDR with Dolby Atmos.
782        HDR_ATMOS_4K = 12;
783        /// Copy the EDID of sink 1; the range runs to [`EdidProfile::SINK_32`].
784        SINK_1 = 101;
785        /// Copy the EDID of sink 32; the range starts at [`EdidProfile::SINK_1`].
786        SINK_32 = 132;
787        /// Base of the range carrying a user-supplied EDID.
788        CUSTOM_0 = 500;
789        /// The device reports no profile.
790        UNKNOWN = 0xFFF;
791    }
792}
793
794impl fmt::Display for EdidProfile {
795    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
796        let name = match *self {
797            Self::STEREO_1080P => "1080p stereo",
798            Self::FIXED => "fixed",
799            Self::UHD_4K => "4K",
800            Self::SURROUND51_1080P => "1080p 5.1",
801            Self::HD_720P => "720p",
802            Self::SURROUND71_1080P => "1080p 7.1",
803            Self::SURROUND71_4K => "4K 7.1",
804            Self::HDR_STEREO_4K => "4K HDR Stereo",
805            Self::HDR_SURROUND71_4K => "4K HDR 7.1",
806            Self::HDR_AVR_ONLY_4K => "4K HDR AVR",
807            Self::LOWEST_COMMON => "lowest common denominator",
808            Self::LOWEST_COMMON_ALL => "lowest common denominator (all sinks)",
809            Self::HDR_ATMOS_4K => "4K HDR Dolby Atmos",
810            _ => {
811                if self.0 >= Self::SINK_1.0 && self.0 <= Self::SINK_32.0 {
812                    return write!(f, "copy from sink #{}", self.0 - Self::SINK_1.0 + 1);
813                }
814                return write!(f, "custom #{}", self.0);
815            }
816        };
817        f.write_str(name)
818    }
819}
820
821wire_enum! {
822    /// A firmware component.
823    FirmwareType: u8 {
824        /// The component is not known.
825        UNKNOWN = 0;
826        /// The FPGA bitstream.
827        FPGA = 1;
828        /// The Linux system image.
829        LINUX = 2;
830        /// A loadable overlay.
831        LOADING_OVERLAY = 3;
832    }
833}
834
835impl fmt::Display for FirmwareType {
836    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
837        let name = match *self {
838            Self::FPGA => "FPGA",
839            Self::LINUX => "Linux",
840            Self::LOADING_OVERLAY => "Loading Overlay",
841            _ => "Unknown",
842        };
843        f.write_str(name)
844    }
845}
846
847wire_enum! {
848    /// The negotiated speed of a network port.
849    UtpLinkSpeed: u8 {
850        /// The device reports no speed.
851        UNKNOWN = 0;
852        /// 10Mbit/s.
853        SPEED_10M = 1;
854        /// 100Mbit/s.
855        SPEED_100M = 2;
856        /// 200Mbit/s.
857        SPEED_200M = 3;
858        /// 1Gbit/s.
859        SPEED_1G = 4;
860    }
861}
862
863impl fmt::Display for UtpLinkSpeed {
864    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
865        let name = match *self {
866            Self::SPEED_10M => "10Mbit/s",
867            Self::SPEED_100M => "100Mbit/s",
868            Self::SPEED_200M => "200Mbit/s",
869            Self::SPEED_1G => "1Gbit/s",
870            _ => "Unknown",
871        };
872        f.write_str(name)
873    }
874}
875
876wire_enum! {
877    /// The window layout of a multiviewer.
878    MultiviewerViewMode: u8 {
879        /// The device reports no layout.
880        UNKNOWN = 0;
881        /// One full-screen window.
882        SINGLE = 1;
883        /// Picture in picture.
884        PIP = 2;
885        /// Two windows, large.
886        TWO_SCREEN_LARGE = 3;
887        /// Two windows, small.
888        TWO_SCREEN_SMALL = 4;
889        /// Three windows, large.
890        THREE_SCREEN_LARGE = 5;
891        /// Three windows, small.
892        THREE_SCREEN_SMALL = 6;
893        /// Four windows, equal size.
894        FOUR_SCREEN_EQUAL = 7;
895        /// Four windows, small.
896        FOUR_SCREEN_SMALL = 8;
897    }
898}
899
900wire_enum! {
901    /// The corner a multiviewer places its picture-in-picture window in.
902    MultiviewerPipPosition: u8 {
903        /// The device reports no position.
904        UNKNOWN = 0;
905        /// Top left.
906        LEFT_TOP = 1;
907        /// Bottom left.
908        LEFT_BOTTOM = 2;
909        /// Top right.
910        RIGHT_TOP = 3;
911        /// Bottom right.
912        RIGHT_BOTTOM = 4;
913    }
914}
915
916wire_enum! {
917    /// The size of a multiviewer's picture-in-picture window.
918    MultiviewerPipSize: u8 {
919        /// The device reports no size.
920        UNKNOWN = 0;
921        /// Small.
922        SMALL = 1;
923        /// Medium.
924        MEDIUM = 2;
925        /// Large.
926        LARGE = 3;
927    }
928}
929
930wire_enum! {
931    /// The resolution and refresh rate a multiviewer drives its output at.
932    MultiviewerOutputMode: u8 {
933        /// The device reports no output mode.
934        UNKNOWN = 0;
935        /// 4096x2160p60.
936        DCI4K_P60 = 1;
937        /// 4096x2160p50.
938        DCI4K_P50 = 2;
939        /// 3840x2160p60.
940        UHD_P60 = 3;
941        /// 3840x2160p50.
942        UHD_P50 = 4;
943        /// 3840x2160p30.
944        UHD_P30 = 5;
945        /// 3840x2160p25.
946        UHD_P25 = 6;
947        /// 1920x1200p60, reduced blanking.
948        WUXGA_P60_RB = 7;
949        /// 1920x1080p60.
950        HD1080_P60 = 8;
951        /// 1920x1080p50.
952        HD1080_P50 = 9;
953        /// 1360x768p60.
954        WXGA_P60 = 10;
955        /// 1280x800p60.
956        WXGA800_P60 = 11;
957        /// 1280x720p60.
958        HD720_P60 = 12;
959        /// 1280x720p50.
960        HD720_P50 = 13;
961        /// 1024x768p60.
962        XGA_P60 = 14;
963    }
964}
965
966wire_enum! {
967    /// The HDCP version a multiviewer output negotiates.
968    MultiviewerHdcpMode: u8 {
969        /// The device reports no HDCP mode.
970        UNKNOWN = 0;
971        /// HDCP 1.4.
972        V14 = 1;
973        /// HDCP 2.2.
974        V22 = 2;
975        /// Content protection off.
976        OFF = 3;
977    }
978}
979
980wire_enum! {
981    /// The IT-content flag a multiviewer sets on its output.
982    MultiviewerItcMode: u8 {
983        /// The device reports no IT-content mode.
984        UNKNOWN = 0;
985        /// Video content.
986        VIDEO = 1;
987        /// PC content.
988        PC = 2;
989    }
990}
991
992wire_enum! {
993    /// The EDID template a multiviewer presents to its sources.
994    ///
995    /// A template's name is the largest resolution it advertises and the audio
996    /// format it declares support for.
997    MultiviewerEdidTemplate: u8 {
998        /// The device reports no template.
999        EDID_UNKNOWN = 0;
1000        /// 4K2K60 4:4:4, stereo 2.0.
1001        EDID_4K2K60_444_STEREO = 1;
1002        /// 4K2K60 4:4:4, Dolby/DTS 5.1.
1003        EDID_4K2K60_444_DOLBY_DTS_51 = 2;
1004        /// 4K2K60 4:4:4, HD audio 7.1.
1005        EDID_4K2K60_444_HD_AUDIO_71 = 3;
1006        /// 4K2K30 4:4:4, stereo 2.0.
1007        EDID_4K2K30_444_STEREO = 4;
1008        /// 4K2K30 4:4:4, Dolby/DTS 5.1.
1009        EDID_4K2K30_444_DOLBY_DTS_51 = 5;
1010        /// 4K2K30 4:4:4, HD audio 7.1.
1011        EDID_4K2K30_444_HD_AUDIO_71 = 6;
1012        /// 1080p, stereo 2.0.
1013        EDID_1080P_STEREO = 7;
1014        /// 1080p, Dolby/DTS 5.1.
1015        EDID_1080P_DOLBY_DTS_51 = 8;
1016        /// 1080p, HD audio 7.1.
1017        EDID_1080P_HD_AUDIO_71 = 9;
1018        /// 1920x1200, stereo 2.0.
1019        EDID_1920X1200_STEREO = 10;
1020        /// 1680x1050, stereo 2.0.
1021        EDID_1680X1050_STEREO = 11;
1022        /// 1600x1200, stereo 2.0.
1023        EDID_1600X1200_STEREO = 12;
1024        /// 1440x900, stereo 2.0.
1025        EDID_1440X900_STEREO = 13;
1026        /// 1360x768, stereo 2.0.
1027        EDID_1360X768_STEREO = 14;
1028        /// 1280x1024, stereo 2.0.
1029        EDID_1280X1024_STEREO = 15;
1030        /// 1024x768, stereo 2.0.
1031        EDID_1024X768_STEREO = 16;
1032        /// 720p, stereo 2.0.
1033        EDID_720P_STEREO = 17;
1034        /// Whatever the display connected to the HDMI output presents. The
1035        /// template a multiviewer leaves the factory with.
1036        EDID_COPY_OUTPUT = 18;
1037        /// The EDID loaded onto the device.
1038        EDID_CUSTOM = 19;
1039    }
1040}
1041
1042wire_enum! {
1043    /// The aspect ratio a multiviewer scales its windows to.
1044    MultiviewerAspectRatio: u8 {
1045        /// The device reports no aspect ratio.
1046        UNKNOWN = 0;
1047        /// Fill the window.
1048        FULL = 1;
1049        /// 16:9.
1050        RATIO_16_9 = 2;
1051    }
1052}
1053
1054wire_enum! {
1055    /// A multiviewer setting that is on, off, or not reported.
1056    MultiviewerBool: u8 {
1057        /// Off.
1058        OFF = 0;
1059        /// On.
1060        ON = 1;
1061        /// The device reports no value.
1062        UNKNOWN = 0xFF;
1063    }
1064}
1065
1066wire_enum! {
1067    /// One of a multiviewer's four inputs.
1068    ///
1069    /// The wire numbers the inputs from zero and this type from one, so that
1070    /// zero can mean "not reported" the way it does for every other
1071    /// multiviewer setting. So `to_wire` and `from_wire` carry this type's
1072    /// numbering rather than the wire's, and neither is the conversion to
1073    /// reach for when a raw multiviewer byte is what is in hand.
1074    MultiviewerSource: u8 {
1075        /// The device reports no source.
1076        UNKNOWN = 0;
1077        /// Input 1.
1078        INPUT_1 = 1;
1079        /// Input 2.
1080        INPUT_2 = 2;
1081        /// Input 3.
1082        INPUT_3 = 3;
1083        /// Input 4.
1084        INPUT_4 = 4;
1085    }
1086}
1087
1088impl MultiviewerSource {
1089    /// Reads a zero-based wire value, mapping anything past input 4 to
1090    /// [`MultiviewerSource::UNKNOWN`].
1091    ///
1092    /// The firmware spells "not known" as 0xFF, which lands past input 4 and
1093    /// so needs no case of its own.
1094    pub(crate) const fn from_zero_based(value: u8) -> Self {
1095        if value > 3 {
1096            Self::UNKNOWN
1097        } else {
1098            Self(value + 1)
1099        }
1100    }
1101
1102    /// The zero-based value the wire carries, or `None` for a source naming no
1103    /// input.
1104    ///
1105    /// A multiviewer reads zero as its first input, so there is no value that
1106    /// says "leave this alone": a request that cannot name an input has to be
1107    /// refused rather than sent.
1108    pub(crate) const fn to_zero_based(self) -> Option<u8> {
1109        match self.0 {
1110            1..=4 => Some(self.0 - 1),
1111            _ => None,
1112        }
1113    }
1114}
1115
1116impl MultiviewerBool {
1117    /// Reads a wire value, mapping anything but 0 and 1 to
1118    /// [`MultiviewerBool::UNKNOWN`].
1119    pub(crate) const fn from_wire_tristate(value: u8) -> Self {
1120        if value > 1 {
1121            Self::UNKNOWN
1122        } else {
1123            Self(value)
1124        }
1125    }
1126}
1127
1128wire_enum! {
1129    /// The colour space a V2IP output scales to.
1130    ///
1131    /// The field is four bits wide and only these four values are defined. A
1132    /// receiver passes the whole nibble to its validator, so a fifth value is
1133    /// dropped without a word rather than clamped to one of these.
1134    V2ipColourSpace: u8 {
1135        /// RGB.
1136        RGB = 0;
1137        /// YCbCr 4:4:4.
1138        YCBCR444 = 1;
1139        /// YCbCr 4:2:2.
1140        YCBCR422 = 2;
1141        /// YCbCr 4:2:0.
1142        YCBCR420 = 3;
1143    }
1144}
1145
1146wire_enum! {
1147    /// The 2-byte `mxr_signal_type` carried in scaling configs and bay signal
1148    /// reports.
1149    ///
1150    /// Byte 0 is the CTA-861 short video descriptor, 0 when the signal is not
1151    /// HDMI. Byte 1 packs `color:4` in the low nibble, then `non_int:1` and
1152    /// `bpp:3` above it.
1153    MxrSignalType: u16 {
1154        /// No signal format was reported.
1155        NONE = 0;
1156    }
1157}
1158
1159/// The bpp index a sender writes when it has no bit depth to report.
1160///
1161/// It sits outside the four indices that name a real depth, so an unset
1162/// format reads differently from every genuine one.
1163const SIG_BPP_UNSET: u16 = 5;
1164
1165impl MxrSignalType {
1166    /// The CTA-861 short video descriptor, 0 when the signal is not HDMI.
1167    pub const fn svd(self) -> u8 {
1168        (self.0 & 0xFF) as u8
1169    }
1170
1171    /// The colour space.
1172    pub const fn colour_space(self) -> u8 {
1173        ((self.0 >> 8) & 0xF) as u8
1174    }
1175
1176    /// Whether the frame rate carries a 1000/1001 clock.
1177    pub const fn is_non_integer(self) -> bool {
1178        self.0 & (1 << 12) != 0
1179    }
1180
1181    /// The raw bpp index as carried on the wire. The field is an index, not a
1182    /// bit depth; [`MxrSignalType::bpp`] converts it.
1183    pub const fn bpp_index(self) -> u8 {
1184        ((self.0 >> 13) & 0x7) as u8
1185    }
1186
1187    /// The bit depth the bpp index stands for, `None` when unknown or unset.
1188    pub const fn bpp(self) -> Option<u8> {
1189        match self.bpp_index() {
1190            1 => Some(8),
1191            2 => Some(10),
1192            3 => Some(12),
1193            4 => Some(16),
1194            _ => None,
1195        }
1196    }
1197
1198    /// Reports whether the word carries a signal format at all.
1199    ///
1200    /// A bay with nothing configured says so two ways. A sender that zeroes
1201    /// the word and stamps the unset bpp index leaves an index no real depth
1202    /// uses, and one that writes a plain zero leaves nothing at all. Neither
1203    /// is a format, and the svd and colour space beside them are not answers
1204    /// either: both read as zero, which is what this word says for "not HDMI"
1205    /// and "RGB" when it *is* set.
1206    pub const fn is_set(self) -> bool {
1207        self.0 != 0 && self.bpp_index() as u16 != SIG_BPP_UNSET
1208    }
1209
1210    /// Builds the word from the fields a scaling write consumes.
1211    ///
1212    /// `non_int` is left clear: the receiving struct carries the bit, and the
1213    /// apply path does not read it.
1214    ///
1215    /// Building rather than editing is the point. A sink with no mode
1216    /// configured reports the word with the unset bpp index in it, so a caller
1217    /// that read that word back and filled in an svd would send an index no
1218    /// depth uses - which the receiver decodes to zero and rejects without
1219    /// answering.
1220    pub(crate) const fn from_parts(svd: u8, colour: u8, bpp_index: u8) -> Self {
1221        Self((svd as u16) | (((colour & 0xF) as u16) << 8) | (((bpp_index & 0x7) as u16) << 13))
1222    }
1223
1224    /// The bpp index that stands for a bit depth, `None` for a depth no index
1225    /// names.
1226    ///
1227    /// Only the three depths a V2IP output stage accepts are here. Index 4
1228    /// names 16bpp, which [`MxrSignalType::bpp`] reads back from a device, but
1229    /// the output stage refuses it - so offering it as something to write would
1230    /// send a frame that is decoded cleanly and then dropped in silence.
1231    pub(crate) const fn bpp_index_for_depth(depth: u8) -> Option<u8> {
1232        match depth {
1233            8 => Some(1),
1234            10 => Some(2),
1235            12 => Some(3),
1236            _ => None,
1237        }
1238    }
1239}
1240
1241impl fmt::Display for MxrSignalType {
1242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1243        if !self.is_set() {
1244            return f.write_str("unset");
1245        }
1246        match self.bpp() {
1247            Some(bpp) => write!(
1248                f,
1249                "svd {}, color {}, {}bpp",
1250                self.svd(),
1251                self.colour_space(),
1252                bpp
1253            ),
1254            None => write!(f, "svd {}, color {}", self.svd(), self.colour_space()),
1255        }
1256    }
1257}