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 OneIP stream source.
134        V2IP_SOURCE = 1 << 3;
135        /// Acts as a OneIP 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        /// Copies the overlay settings and logos of another unit when asked.
192        OSD_SYNC = 1 << 29;
193        /// Is downloading or installing a firmware or video processor
194        /// upgrade. [`STATUS_REBOOT`](Self::STATUS_REBOOT) follows when it
195        /// reboots to complete it.
196        STATUS_UPGRADING = 1 << 30;
197        /// Flips on every reboot, so a peer that sees it change knows the device restarted.
198        BOOT_BIT = 1 << 31;
199    }
200}
201
202bitmask! {
203    /// What a OneIP device's video processor supports, as the device reports it
204    /// in its configuration.
205    ///
206    /// Read-only, and a device's own: it fills the field in only on the frame
207    /// describing itself, and leaves it zero on one it sends to configure
208    /// another device. There is no write path.
209    ///
210    /// Bits are assigned by the video processor and only ever appended, so a
211    /// bit this library has no name for is a later capability rather than an
212    /// error. A device reports no features at all until its processor answers,
213    /// and an older processor answers with none of the optional commands, so an
214    /// empty mask is never reported as a capability set - see
215    /// [`crate::Remote::v2ip_features`], which reports it as unknown instead.
216    V2ipFpgaFeature: u64 {
217        /// Applies a DSCP marking to the streams it sources.
218        SOURCE_DSCP = 1 << 0;
219        /// Reports the audio format arriving at its sink.
220        SINK_AUDIO_FORMAT = 1 << 1;
221        /// Places a tiling window on its sink.
222        SINK_TILING_WINDOW = 1 << 2;
223        /// Reports the state of its sink's overlay.
224        SINK_OVERLAY_STATE = 1 << 3;
225        /// Reports its sink's state.
226        SINK_STATE = 1 << 4;
227        /// Reports information about the stream its sink receives.
228        SINK_STREAM_INFO = 1 << 5;
229        /// Draws a test pattern, tone and lip-sync flash on its sink's output.
230        SINK_TEST_PATTERN = 1 << 8;
231    }
232}
233
234bitmask! {
235    /// The device settings a OneIP configuration can carry, each on its own bit.
236    ///
237    /// The same bits serve as the settings a frame carries and as the values
238    /// of the on/off ones among them. `IR_PROFILE` to `POWER_SAVE_SCHEDULE`
239    /// carry a value elsewhere in the block and have no on/off value of their
240    /// own.
241    ///
242    /// A device reports every setting it has, so one it never reports is one
243    /// it does not have.
244    V2ipDeviceSetting {
245        /// Disables the decoder while the display is off.
246        SINK_CHECK_POWER = 1 << 0;
247        /// Disables the HDMI output while there is no signal.
248        SINK_OFF_NO_SIGNAL = 1 << 1;
249        /// Sends infrared modulated.
250        IR_TX_MODULATED = 1 << 2;
251        /// Lights the status LED.
252        STATUS_LED = 1 << 3;
253        /// Lights the network port LEDs.
254        NETWORK_LED = 1 << 4;
255        /// Runs the fan in quiet mode.
256        FAN_QUIET = 1 << 5;
257        /// Accepts CEC combo keys.
258        CEC_COMBO_KEYS = 1 << 6;
259        /// Accepts CEC combo keys for the device's own input.
260        CEC_COMBO_INPUT = 1 << 7;
261        /// The infrared profile of the device's global infrared port.
262        IR_PROFILE = 1 << 8;
263        /// The infrared profile of the output's infrared port.
264        IR_PROFILE_SINK = 1 << 9;
265        /// The infrared profiles stored on the device. Reported by the device
266        /// itself and never written.
267        IR_PROFILES = 1 << 10;
268        /// The minutes a device stays idle before it powers down by itself.
269        AUTO_POWER_SAVE = 1 << 11;
270        /// The daily windows in which the device powers down.
271        POWER_SAVE_SCHEDULE = 1 << 12;
272        /// The device's clock has been set, on/off. Reported by the device
273        /// itself and never written.
274        CLOCK_SET = 1 << 13;
275        /// The PTP mode the device runs.
276        PTP_MODE = 1 << 14;
277        /// The PTP domain the device runs in.
278        PTP_DOMAIN = 1 << 15;
279        /// The priority1 the device announces as a PTP grandmaster.
280        PTP_PRIORITY1 = 1 << 16;
281        /// Sends the source's audio as an AES67 flow, where the source allows.
282        AES67 = 1 << 17;
283        /// The level the device powers down to in power save. A device picks
284        /// it itself: the level that saves the most while its input and
285        /// display can still wake it. Written without
286        /// [`POWER_SAVE_DISABLED`](Self::POWER_SAVE_DISABLED), it switches
287        /// power save on, and the level written is ignored, except by a
288        /// device whose firmware predates picking it, which takes the level
289        /// as its setting.
290        POWER_SAVE_LEVEL = 1 << 18;
291        /// The interval between the device's PTP announce messages.
292        PTP_ANNOUNCE = 1 << 19;
293        /// The interval between the device's PTP sync messages.
294        PTP_SYNC = 1 << 20;
295        /// The interval between the device's PTP delay requests.
296        PTP_DELAY_REQ = 1 << 21;
297        /// The announce intervals before the device drops a silent PTP master.
298        PTP_ANNOUNCE_TIMEOUT = 1 << 22;
299        /// The DSCP of the device's PTP messages.
300        PTP_DSCP = 1 << 23;
301        /// The minutes after which the device forgets a SAP announcement it
302        /// heard.
303        SAP_EXPIRE = 1 << 24;
304        /// Listens for SAP announcements.
305        SAP_LISTEN = 1 << 25;
306        /// Sends SAP announcements.
307        SAP_ANNOUNCE = 1 << 26;
308        /// The seconds between the device's SAP announcements.
309        SAP_INTERVAL = 1 << 27;
310        /// Never powers down by itself: automatic power save and the power
311        /// save schedule leave the device running, and switching this on
312        /// wakes a device either has powered down. A power save request
313        /// still powers it down. Writing
314        /// [`POWER_SAVE_LEVEL`](Self::POWER_SAVE_LEVEL) switches it off.
315        POWER_SAVE_DISABLED = 1 << 28;
316        /// The TTL of the device's streams, video, audio and ancillary
317        /// alike. Reported only by a device with an encoder.
318        STREAM_TTL = 1 << 29;
319        /// Connects to the cloud service. A device that does not report it
320        /// connects regardless.
321        CLOUD = 1 << 30;
322        /// The angle the device's HDMI output is turned by. Reported only by
323        /// a device with an HDMI output.
324        SINK_ROTATION = 1 << 31;
325    }
326}
327
328wire_enum! {
329    /// When a OneIP output mixes 5.1 and 7.1 PCM down to stereo.
330    V2ipDownmixMode: u8 {
331        /// Never.
332        OFF = 0;
333        /// When the display's EDID lists two PCM channels or fewer.
334        AUTO = 1;
335        /// Always.
336        ALWAYS = 2;
337    }
338}
339
340wire_enum! {
341    /// How loud a OneIP output's stereo downmix is.
342    V2ipDownmixLevel: u8 {
343        /// Scaled so the mix never clips.
344        NORMALISED = 0;
345        /// Unscaled, with dialogue at the level it has in stereo.
346        UNSCALED = 1;
347    }
348}
349
350wire_enum! {
351    /// How far a OneIP device powers down in power save, each level saving more
352    /// than the one before.
353    V2ipPowerSaveLevel: u8 {
354        /// No level.
355        NONE = 0;
356        /// Part of the video processing powers down.
357        REDUCED = 1;
358        /// All of the video processing powers down.
359        LOW = 2;
360        /// Everything but the network powers down.
361        OFF = 3;
362    }
363}
364
365bitmask! {
366    /// What a unit offers at one power save level.
367    UnitPowerSave: u8 {
368        /// The unit can power down to this level.
369        SUPPORTED = 1 << 0;
370        /// A signal on the HDMI input wakes the unit.
371        INPUT_WAKE = 1 << 1;
372        /// The display powering on wakes the unit, and the unit can power it
373        /// on.
374        OUTPUT_WAKE = 1 << 2;
375    }
376}
377
378wire_enum! {
379    /// How a OneIP device takes part in PTP.
380    V2ipPtpMode: u8 {
381        /// No PTP.
382        OFF = 0;
383        /// Follows a grandmaster, and never becomes one.
384        FOLLOWER = 1;
385        /// Follows a grandmaster, or becomes one when there is none.
386        AUTO = 2;
387    }
388}
389
390wire_enum! {
391    /// The test pattern a OneIP sink draws on its output.
392    V2ipTestPattern: u8 {
393        /// No pattern.
394        OFF = 0;
395        /// Colour bars.
396        BARS = 1;
397        /// One flat colour, the one the test card carries.
398        FLAT = 2;
399        /// A ramp.
400        RAMP = 3;
401        /// A grid.
402        GRID = 4;
403        /// A strip.
404        STRIP = 5;
405        /// A test card.
406        CARD = 6;
407    }
408}
409
410wire_enum! {
411    /// The test tone a OneIP sink plays on its output.
412    V2ipToneMode: u8 {
413        /// No tone.
414        OFF = 0;
415        /// A continuous tone.
416        CONTINUOUS = 1;
417        /// A channel ident.
418        IDENT = 2;
419        /// A line-up tone, which needs two channels or more.
420        LINEUP = 3;
421        /// A beep on each lip-sync mark.
422        BEEP = 4;
423    }
424}
425
426bitmask! {
427    /// What a OneIP sink reports about its test card.
428    V2ipTestcardFlag: u8 {
429        /// The sink can draw the test card.
430        SUPPORTED = 1 << 0;
431        /// The output shows the pattern.
432        SHOWING = 1 << 1;
433        /// The tone plays on the output.
434        PLAYING = 1 << 2;
435        /// A pattern change has yet to reach the video processor.
436        PATTERN_PENDING = 1 << 3;
437        /// A tone change has yet to reach the video processor.
438        TONE_PENDING = 1 << 4;
439        /// A lip-sync change has yet to reach the video processor.
440        SYNC_PENDING = 1 << 5;
441    }
442}
443
444bitmask! {
445    /// The flags of what a OneIP output plays from a session description.
446    V2ipSdpFlag: u8 {
447        /// The output plays a stream from a description.
448        ACTIVE = 1 << 0;
449        /// And nothing else, as no video is routed to it.
450        AUDIO_ONLY = 1 << 1;
451        /// The clock mode and state hold the video processor's answer.
452        CLOCK = 1 << 2;
453    }
454}
455
456wire_enum! {
457    /// The sample encoding of a stream a OneIP output plays from a description.
458    V2ipSdpEncoding: u8 {
459        /// None.
460        NONE = 0;
461        /// 16-bit linear PCM.
462        L16 = 1;
463        /// 24-bit linear PCM.
464        L24 = 2;
465        /// AM824.
466        AM824 = 3;
467    }
468}
469
470wire_enum! {
471    /// Where a described stream's media clock comes from.
472    V2ipSdpMediaClock: u8 {
473        /// None given.
474        NONE = 0;
475        /// Direct, at an offset from the reference clock.
476        DIRECT = 1;
477        /// The sender's.
478        SENDER = 2;
479    }
480}
481
482wire_enum! {
483    /// The reference clock a described stream names.
484    V2ipSdpRefClock: u8 {
485        /// None given.
486        NONE = 0;
487        /// PTP.
488        PTP = 1;
489        /// A traceable clock.
490        TRACEABLE = 2;
491        /// The sender's local MAC.
492        LOCAL_MAC = 3;
493    }
494}
495
496wire_enum! {
497    /// What a OneIP output's video processor times a described stream by.
498    V2ipSdpClockMode: u8 {
499        /// Arrival.
500        ARRIVAL = 0;
501        /// PTP.
502        PTP = 1;
503        /// The sender.
504        SENDER = 2;
505    }
506}
507
508wire_enum! {
509    /// How far a OneIP output's video processor has got with a described
510    /// stream's timing.
511    V2ipSdpClockState: u8 {
512        /// Idle.
513        IDLE = 0;
514        /// Running its servo.
515        SERVO = 1;
516        /// Aligning.
517        ALIGNING = 2;
518        /// Timed.
519        TIMED = 3;
520        /// Parked.
521        PARKED = 4;
522        /// Playing untimed.
523        UNTIMED = 5;
524        /// The timing is not supported.
525        UNSUPPORTED = 6;
526    }
527}
528
529bitmask! {
530    /// The flags of a unit's status report.
531    UnitFlag {
532        /// A changed setting takes effect after a reboot.
533        REBOOT_REQUIRED = 1 << 0;
534        /// A newer video processor image is available.
535        FPGA_UPGRADE = 1 << 1;
536        /// The IP configuration comes from DHCP.
537        DHCP = 1 << 2;
538        /// The IP configuration was changed through the mesh, and is reverted
539        /// unless the mesh controller confirms it in time.
540        NET_PENDING = 1 << 3;
541        /// The fan runs in quiet mode.
542        FAN_QUIET = 1 << 4;
543        /// Powered by an external supply rather than PoE.
544        EXTERNAL_PSU = 1 << 5;
545        /// The video processor runs with its IP configured. Without it the
546        /// encoder and decoder health carry nothing.
547        FPGA_CONFIGURED = 1 << 6;
548        /// The encoder is enabled.
549        ENCODER_ACTIVE = 1 << 7;
550        /// The decoder is enabled.
551        DECODER_ACTIVE = 1 << 8;
552        /// The decoder disables itself without a display.
553        AUTO_DISABLE = 1 << 9;
554        /// The decoder disabled itself without a display.
555        AUTO_DISABLE_ACTIVE = 1 << 10;
556        /// The HDMI output switches off without a signal.
557        OFF_NO_SIGNAL = 1 << 11;
558        /// The HDMI output switched off without a signal.
559        OFF_NO_SIGNAL_ACTIVE = 1 << 12;
560        /// Display power is checked before the decoder is enabled.
561        POWER_CHECK = 1 << 13;
562        /// The boot count is reported.
563        BOOT_COUNT = 1 << 14;
564        /// The fan speed is reported.
565        FAN_SPEED = 1 << 15;
566        /// The unit takes [`UnitCommandKind::LOCATE`].
567        LOCATE_SUPPORTED = 1 << 16;
568        /// The unit is being located: its board-ID LED flashes, and an output
569        /// shows "Locate Device".
570        LOCATE = 1 << 17;
571    }
572}
573
574wire_enum! {
575    /// What a unit command asks of its unit.
576    UnitCommandKind: u8 {
577        /// Clear the current system status.
578        CLEAR_STATUS = 0;
579        /// Upgrade the video processor to the latest image.
580        FPGA_UPGRADE = 1;
581        /// Change the IP configuration.
582        NET_CONFIG = 2;
583        /// Keep a pending IP configuration.
584        NET_CONFIRM = 3;
585        /// Start or stop locating the unit.
586        LOCATE = 4;
587    }
588}
589
590bitmask! {
591    /// The flags of a unit command.
592    UnitCommandFlag: u8 {
593        /// With [`UnitCommandKind::FPGA_UPGRADE`]: flash the image even if it
594        /// is running already.
595        FORCE = 1 << 0;
596        /// With [`UnitCommandKind::NET_CONFIG`]: use DHCP, ignoring the
597        /// addresses.
598        DHCP = 1 << 1;
599        /// With [`UnitCommandKind::LOCATE`]: locate it until stopped. Neither
600        /// this nor [`LOCATE_TIMED`](Self::LOCATE_TIMED) stops it.
601        LOCATE_ON = 1 << 2;
602        /// With [`UnitCommandKind::LOCATE`]: locate it for 30 s.
603        LOCATE_TIMED = 1 << 3;
604    }
605}
606
607bitmask! {
608    /// The flags of a OneIP device's PTP state.
609    V2ipPtpFlag: u8 {
610        /// The device runs PTP and the block holds its state. A block without
611        /// it holds nothing: the device has no PTP, or is in power save.
612        VALID = 1 << 0;
613        /// The device locked to a master since its PTP started, so its mesh
614        /// holds a time.
615        FOLLOWED = 1 << 1;
616    }
617}
618
619wire_enum! {
620    /// How far a OneIP device's PTP clock has locked to its master.
621    V2ipPtpLock: u8 {
622        /// Not locked.
623        UNLOCKED = 0;
624        /// Locking.
625        LOCKING = 1;
626        /// Locked.
627        LOCKED = 2;
628    }
629}
630
631bitmask! {
632    /// The flags of a OneIP device's VLAN configuration.
633    V2ipVlanFlag: u16 {
634        /// The block carries a configuration. A block without it carries
635        /// nothing, whatever its other bytes hold.
636        VALID = 1 << 0;
637        /// Untagged frames arriving on the uplink are dropped instead of
638        /// reaching the device.
639        TRUNK = 1 << 1;
640        /// Reported by the device: the configuration is applied, and reverted
641        /// unless the mesh controller confirms it. Never written.
642        PENDING = 1 << 2;
643        /// Sent by the mesh controller: a pending configuration it heard, which
644        /// the device then keeps.
645        CONFIRM = 1 << 3;
646        /// Reported by the device: it has an SFP port. Never written.
647        HAS_SFP = 1 << 4;
648    }
649}
650
651impl V2ipDeviceSetting {
652    /// The settings that are on or off, as opposed to carrying a value, and
653    /// that can be written.
654    pub const SWITCHES: Self = Self(
655        0xFF | Self::AES67.0
656            | Self::SAP_LISTEN.0
657            | Self::SAP_ANNOUNCE.0
658            | Self::POWER_SAVE_DISABLED.0
659            | Self::CLOUD.0,
660    );
661
662    /// The settings only a device reports about itself, which no write
663    /// carries.
664    pub const REPORTED_ONLY: Self = Self(Self::IR_PROFILES.0 | Self::CLOCK_SET.0);
665
666    /// The PTP settings.
667    pub const PTP_SETTINGS: Self =
668        Self(Self::PTP_MODE.0 | Self::PTP_DOMAIN.0 | Self::PTP_PRIORITY1.0);
669
670    /// The PTP message settings.
671    pub const PTP_MESSAGES: Self = Self(
672        Self::PTP_ANNOUNCE.0
673            | Self::PTP_SYNC.0
674            | Self::PTP_DELAY_REQ.0
675            | Self::PTP_ANNOUNCE_TIMEOUT.0
676            | Self::PTP_DSCP.0,
677    );
678
679    /// The settings whose values a frame carries in a block of their own
680    /// behind the rest, [`V2ipPtpMessages`](crate::V2ipPtpMessages): the PTP
681    /// message settings, the two SAP values, the stream TTL and the output's
682    /// rotation.
683    pub const SETTINGS_BLOCK: Self = Self(
684        Self::PTP_MESSAGES.0
685            | Self::SAP_EXPIRE.0
686            | Self::SAP_INTERVAL.0
687            | Self::STREAM_TTL.0
688            | Self::SINK_ROTATION.0,
689    );
690
691    /// These bits with every bit of `other` cleared.
692    pub(crate) const fn without(self, other: Self) -> Self {
693        Self(self.0 & !other.0)
694    }
695}
696
697bitmask! {
698    /// Capabilities of a single bay.
699    BayFeatures {
700        /// HDMI output.
701        HDMI_OUT = 1 << 0;
702        /// HDMI input.
703        HDMI_IN = 1 << 1;
704        /// Digital audio output.
705        AUDIO_DIG_OUT = 1 << 2;
706        /// Digital audio input.
707        AUDIO_DIG_IN = 1 << 3;
708        /// Analogue audio output.
709        AUDIO_ANA_OUT = 1 << 4;
710        /// Analogue audio input.
711        AUDIO_ANA_IN = 1 << 5;
712        /// Infrared input.
713        IR_IN = 1 << 6;
714        /// Infrared output.
715        IR_OUT = 1 << 7;
716        /// Amplified audio output.
717        AUDIO_AMP_OUT = 1 << 8;
718        /// Remote-control output.
719        RC_OUT = 1 << 9;
720        /// Remote-control input.
721        RC_IN = 1 << 10;
722        /// Dolby decoding.
723        DOLBY = 1 << 11;
724        /// Switches itself off when idle.
725        AUTO_OFF = 1 << 12;
726        /// Is a remote OneIP source.
727        V2IP_SOURCE_REMOTE = 1 << 13;
728        /// Is a remote OneIP sink.
729        V2IP_SINK_REMOTE = 1 << 14;
730        /// Is a local OneIP source.
731        V2IP_SOURCE_LOCAL = 1 << 15;
732        /// Is a local OneIP sink.
733        V2IP_SINK_LOCAL = 1 << 16;
734    }
735}
736
737bitmask! {
738    /// Live status flags of a single bay.
739    ///
740    /// Bits 16-19 and 22-23 are bit-fields rather than flags; read them with
741    /// [`BayStatus::rc_type`] and [`BayStatus::hdcp`].
742    BayStatus {
743        /// The bay reports a fault.
744        FAULT = 1 << 0;
745        /// The bay is hidden from the user interface.
746        HIDDEN = 1 << 1;
747        /// The amplifier zone the bay drives is powered on.
748        POWERED = 1 << 2;
749        /// A signal is present.
750        SIGNAL_DETECTED = 1 << 3;
751        /// Hot-plug detect is asserted.
752        HPD_DETECTED = 1 << 4;
753        /// The signal is scrambled.
754        SIGNAL_SCRAMBLE = 1 << 5;
755        /// An HDBaseT link is up.
756        HDBT_CONNECTED = 1 << 6;
757        /// A CEC device answered.
758        CEC_DETECTED = 1 << 7;
759        /// The attached device was powered on.
760        POWERED_ON = 1 << 8;
761        /// The attached device was powered off.
762        POWERED_OFF = 1 << 9;
763        /// Audio return over HDMI is active.
764        AUDIO_ARC_HDMI = 1 << 10;
765        /// Audio return over optical is active.
766        AUDIO_ARC_OPTIC = 1 << 11;
767        /// Audio return over analogue is active.
768        AUDIO_ARC_ANALOG = 1 << 12;
769        /// The bay is offline.
770        OFFLINE = 1 << 13;
771        /// The OneIP decoder is disabled.
772        DECODER_DISABLE = 1 << 14;
773        /// The OneIP encoder is disabled.
774        ENCODER_DISABLE = 1 << 15;
775        /// CEC is switched off for this bay.
776        CEC_DISABLED = 1 << 20;
777        /// The OneIP encoder reports an error.
778        ENCODER_ERROR = 1 << 21;
779        /// The bay's name was generated - a default, or taken from CEC or the
780        /// EDID - rather than set by a user. A device that predates this bit
781        /// never sets it, so a clear bit does not prove a user set the name.
782        AUTO_NAME = 1 << 24;
783    }
784}
785
786impl BayStatus {
787    const RC_TYPE_SHIFT: u32 = 16;
788    const RC_TYPE_MASK: u32 = 0xF << Self::RC_TYPE_SHIFT;
789    const HDCP_SHIFT: u32 = 22;
790    const HDCP_MASK: u32 = 0x3 << Self::HDCP_SHIFT;
791
792    /// Extracts the remote-control type carried in bits 16-19.
793    pub const fn rc_type(self) -> RcType {
794        RcType(((self.0 & Self::RC_TYPE_MASK) >> Self::RC_TYPE_SHIFT) as u8)
795    }
796
797    /// Extracts the HDCP version carried in bits 22-23.
798    pub const fn hdcp(self) -> u8 {
799        ((self.0 & Self::HDCP_MASK) >> Self::HDCP_SHIFT) as u8
800    }
801}
802
803bitmask! {
804    /// Media carried by a virtual link.
805    LinkFeature {
806        /// Video over HDMI.
807        VIDEO_HDMI = 1 << 0;
808        /// Audio over optical.
809        AUDIO_OPTICAL = 1 << 1;
810        /// Audio over analogue.
811        AUDIO_ANALOG = 1 << 2;
812        /// Infrared.
813        IR = 1 << 3;
814        /// Remote control.
815        RC = 1 << 4;
816    }
817}
818
819wire_enum! {
820    /// A remote-control action.
821    RcAction: u16 {
822        /// Toggle power.
823        POWER_TOGGLE = 0;
824        /// Power on.
825        POWER_ON = 1;
826        /// Power off.
827        POWER_OFF = 2;
828        /// Volume up.
829        VOLUME_UP = 3;
830        /// Volume down.
831        VOLUME_DOWN = 4;
832        /// Toggle mute.
833        VOLUME_MUTE = 5;
834    }
835}
836
837wire_enum! {
838    /// A remote-control key code (CEC or IR).
839    RcKey: u16 {
840        /// Digit 0.
841        NUM0 = 0;
842        /// Digit 1.
843        NUM1 = 1;
844        /// Digit 2.
845        NUM2 = 2;
846        /// Digit 3.
847        NUM3 = 3;
848        /// Digit 4.
849        NUM4 = 4;
850        /// Digit 5.
851        NUM5 = 5;
852        /// Digit 6.
853        NUM6 = 6;
854        /// Digit 7.
855        NUM7 = 7;
856        /// Digit 8.
857        NUM8 = 8;
858        /// Digit 9.
859        NUM9 = 9;
860        /// Confirm the highlighted item.
861        SELECT = 10;
862        /// Go back one step.
863        BACK = 11;
864        /// Navigate up.
865        UP = 12;
866        /// Navigate down.
867        DOWN = 13;
868        /// Navigate left.
869        LEFT = 14;
870        /// Navigate right.
871        RIGHT = 15;
872        /// Open the main menu.
873        MENU = 16;
874        /// Open the content menu.
875        CONTENT_MENU = 17;
876        /// Next channel.
877        CHANNEL_UP = 18;
878        /// Previous channel.
879        CHANNEL_DOWN = 19;
880        /// Start playback.
881        PLAY = 20;
882        /// Pause playback.
883        PAUSE = 21;
884        /// Stop playback.
885        STOP = 22;
886        /// Start recording.
887        RECORD = 23;
888        /// Fast forward.
889        FAST_FORWARD = 24;
890        /// Rewind.
891        REWIND = 25;
892        /// Red colour key.
893        RED = 26;
894        /// Green colour key.
895        GREEN = 27;
896        /// Yellow colour key.
897        YELLOW = 28;
898        /// Blue colour key.
899        BLUE = 29;
900        /// Open help.
901        HELP = 30;
902        /// Show information.
903        INFORMATION = 31;
904        /// Open teletext.
905        TEXT = 32;
906        /// Open the programme guide.
907        GUIDE = 33;
908        /// Open video on demand.
909        VIDEO_ON_DEMAND = 34;
910        /// Return to the previous channel.
911        PREVIOUS_CHANNEL = 80;
912        /// Toggle 3D mode.
913        MODE_3D = 81;
914        /// Toggle subtitles.
915        SUBTITLE = 82;
916        /// Select an audio track.
917        SOUND_SELECT = 83;
918        /// Select an input.
919        INPUT_SELECT = 84;
920        /// Eject the medium.
921        EJECT = 85;
922        /// Next chapter.
923        NEXT_CHAPTER = 86;
924        /// Previous chapter.
925        PREV_CHAPTER = 87;
926        /// Open interactive services.
927        INTERACTIVE = 128;
928        /// Open search.
929        SEARCH = 129;
930        /// Sky home key.
931        SKY = 130;
932        /// Base of the range carrying a raw CEC user-control code.
933        CUSTOM_CEC = 1280;
934        /// Base of the range carrying a raw Sky key code.
935        CUSTOM_SKY = 2048;
936    }
937}
938
939wire_enum! {
940    /// The remote-control protocol of a connected sink or source.
941    RcType: u8 {
942        /// Infrared.
943        IR = 0;
944        /// HDMI CEC.
945        CEC = 1;
946        /// Sky UK over IP.
947        SKY_UK = 2;
948        /// TiVo.
949        TIVO = 3;
950        /// Kodi.
951        KODI = 4;
952        /// Dish.
953        DISH = 5;
954        /// DirecTV.
955        DIRECTV = 6;
956        /// Another MX Remote device.
957        MX_REMOTE = 7;
958        /// Routed inside the device itself.
959        INTERNAL = 8;
960    }
961}
962
963impl fmt::Display for RcType {
964    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
965        let name = match *self {
966            Self::IR => "IR",
967            Self::CEC => "CEC",
968            Self::SKY_UK => "Sky",
969            Self::TIVO => "TiVo",
970            Self::KODI => "Kodi",
971            Self::DISH => "Dish",
972            Self::DIRECTV => "DirecTV",
973            Self::MX_REMOTE => "MX-Remote",
974            Self::INTERNAL => "Internal",
975            _ => "Unknown",
976        };
977        f.write_str(name)
978    }
979}
980
981wire_enum! {
982    /// An EDID preset selectable on an HDMI input.
983    EdidProfile: u16 {
984        /// 1080p with stereo audio.
985        STEREO_1080P = 0;
986        /// A fixed EDID stored on the device.
987        FIXED = 1;
988        /// 4K.
989        UHD_4K = 2;
990        /// 1080p with 5.1 audio.
991        SURROUND51_1080P = 3;
992        /// 720p.
993        HD_720P = 4;
994        /// 1080p with 7.1 audio.
995        SURROUND71_1080P = 5;
996        /// 4K with 7.1 audio.
997        SURROUND71_4K = 6;
998        /// 4K HDR with stereo audio.
999        HDR_STEREO_4K = 7;
1000        /// 4K HDR with 7.1 audio.
1001        HDR_SURROUND71_4K = 8;
1002        /// 4K HDR, audio to the AVR only.
1003        HDR_AVR_ONLY_4K = 9;
1004        /// The lowest common denominator of the connected sinks.
1005        LOWEST_COMMON = 10;
1006        /// The lowest common denominator of every sink, connected or not.
1007        LOWEST_COMMON_ALL = 11;
1008        /// 4K HDR with Dolby Atmos.
1009        HDR_ATMOS_4K = 12;
1010        /// Copy the EDID of sink 1; the range runs to [`EdidProfile::SINK_32`].
1011        SINK_1 = 101;
1012        /// Copy the EDID of sink 32; the range starts at [`EdidProfile::SINK_1`].
1013        SINK_32 = 132;
1014        /// Base of the range carrying a user-supplied EDID.
1015        CUSTOM_0 = 500;
1016        /// The device reports no profile.
1017        UNKNOWN = 0xFFF;
1018    }
1019}
1020
1021impl fmt::Display for EdidProfile {
1022    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1023        let name = match *self {
1024            Self::STEREO_1080P => "1080p stereo",
1025            Self::FIXED => "fixed",
1026            Self::UHD_4K => "4K",
1027            Self::SURROUND51_1080P => "1080p 5.1",
1028            Self::HD_720P => "720p",
1029            Self::SURROUND71_1080P => "1080p 7.1",
1030            Self::SURROUND71_4K => "4K 7.1",
1031            Self::HDR_STEREO_4K => "4K HDR Stereo",
1032            Self::HDR_SURROUND71_4K => "4K HDR 7.1",
1033            Self::HDR_AVR_ONLY_4K => "4K HDR AVR",
1034            Self::LOWEST_COMMON => "lowest common denominator",
1035            Self::LOWEST_COMMON_ALL => "lowest common denominator (all sinks)",
1036            Self::HDR_ATMOS_4K => "4K HDR Dolby Atmos",
1037            _ => {
1038                if self.0 >= Self::SINK_1.0 && self.0 <= Self::SINK_32.0 {
1039                    return write!(f, "copy from sink #{}", self.0 - Self::SINK_1.0 + 1);
1040                }
1041                return write!(f, "custom #{}", self.0);
1042            }
1043        };
1044        f.write_str(name)
1045    }
1046}
1047
1048wire_enum! {
1049    /// A firmware component.
1050    FirmwareType: u8 {
1051        /// The unit's own firmware.
1052        MCU = 0;
1053        /// The video processor's FPGA image.
1054        FPGA = 1;
1055        /// The video processor's RTOS image.
1056        RTOS = 2;
1057        /// The video processor's loader image.
1058        LOADER = 3;
1059        /// The unit's bootloader.
1060        BOOTLOADER = 4;
1061    }
1062}
1063
1064impl fmt::Display for FirmwareType {
1065    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1066        let name = match *self {
1067            Self::MCU => "MCU",
1068            Self::FPGA => "FPGA",
1069            Self::RTOS => "RTOS",
1070            Self::LOADER => "Loader",
1071            Self::BOOTLOADER => "Bootloader",
1072            _ => return write!(f, "firmware #{}", self.0),
1073        };
1074        f.write_str(name)
1075    }
1076}
1077
1078wire_enum! {
1079    /// The negotiated speed of a network port.
1080    UtpLinkSpeed: u8 {
1081        /// The device reports no speed.
1082        UNKNOWN = 0;
1083        /// 10Mbit/s.
1084        SPEED_10M = 1;
1085        /// 100Mbit/s.
1086        SPEED_100M = 2;
1087        /// 200Mbit/s.
1088        SPEED_200M = 3;
1089        /// 1Gbit/s.
1090        SPEED_1G = 4;
1091    }
1092}
1093
1094impl fmt::Display for UtpLinkSpeed {
1095    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1096        let name = match *self {
1097            Self::SPEED_10M => "10Mbit/s",
1098            Self::SPEED_100M => "100Mbit/s",
1099            Self::SPEED_200M => "200Mbit/s",
1100            Self::SPEED_1G => "1Gbit/s",
1101            _ => "Unknown",
1102        };
1103        f.write_str(name)
1104    }
1105}
1106
1107wire_enum! {
1108    /// The window layout of a multiviewer.
1109    MultiviewerViewMode: u8 {
1110        /// The device reports no layout.
1111        UNKNOWN = 0;
1112        /// One full-screen window.
1113        SINGLE = 1;
1114        /// Picture in picture.
1115        PIP = 2;
1116        /// Two windows, large.
1117        TWO_SCREEN_LARGE = 3;
1118        /// Two windows, small.
1119        TWO_SCREEN_SMALL = 4;
1120        /// Three windows, large.
1121        THREE_SCREEN_LARGE = 5;
1122        /// Three windows, small.
1123        THREE_SCREEN_SMALL = 6;
1124        /// Four windows, equal size.
1125        FOUR_SCREEN_EQUAL = 7;
1126        /// Four windows, small.
1127        FOUR_SCREEN_SMALL = 8;
1128    }
1129}
1130
1131wire_enum! {
1132    /// The corner a multiviewer places its picture-in-picture window in.
1133    MultiviewerPipPosition: u8 {
1134        /// The device reports no position.
1135        UNKNOWN = 0;
1136        /// Top left.
1137        LEFT_TOP = 1;
1138        /// Bottom left.
1139        LEFT_BOTTOM = 2;
1140        /// Top right.
1141        RIGHT_TOP = 3;
1142        /// Bottom right.
1143        RIGHT_BOTTOM = 4;
1144    }
1145}
1146
1147wire_enum! {
1148    /// The size of a multiviewer's picture-in-picture window.
1149    MultiviewerPipSize: u8 {
1150        /// The device reports no size.
1151        UNKNOWN = 0;
1152        /// Small.
1153        SMALL = 1;
1154        /// Medium.
1155        MEDIUM = 2;
1156        /// Large.
1157        LARGE = 3;
1158    }
1159}
1160
1161wire_enum! {
1162    /// The resolution and refresh rate a multiviewer drives its output at.
1163    MultiviewerOutputMode: u8 {
1164        /// The device reports no output mode.
1165        UNKNOWN = 0;
1166        /// 4096x2160p60.
1167        DCI4K_P60 = 1;
1168        /// 4096x2160p50.
1169        DCI4K_P50 = 2;
1170        /// 3840x2160p60.
1171        UHD_P60 = 3;
1172        /// 3840x2160p50.
1173        UHD_P50 = 4;
1174        /// 3840x2160p30.
1175        UHD_P30 = 5;
1176        /// 3840x2160p25.
1177        UHD_P25 = 6;
1178        /// 1920x1200p60, reduced blanking.
1179        WUXGA_P60_RB = 7;
1180        /// 1920x1080p60.
1181        HD1080_P60 = 8;
1182        /// 1920x1080p50.
1183        HD1080_P50 = 9;
1184        /// 1360x768p60.
1185        WXGA_P60 = 10;
1186        /// 1280x800p60.
1187        WXGA800_P60 = 11;
1188        /// 1280x720p60.
1189        HD720_P60 = 12;
1190        /// 1280x720p50.
1191        HD720_P50 = 13;
1192        /// 1024x768p60.
1193        XGA_P60 = 14;
1194    }
1195}
1196
1197wire_enum! {
1198    /// The HDCP version a multiviewer output negotiates.
1199    MultiviewerHdcpMode: u8 {
1200        /// The device reports no HDCP mode.
1201        UNKNOWN = 0;
1202        /// HDCP 1.4.
1203        V14 = 1;
1204        /// HDCP 2.2.
1205        V22 = 2;
1206        /// Content protection off.
1207        OFF = 3;
1208    }
1209}
1210
1211wire_enum! {
1212    /// The IT-content flag a multiviewer sets on its output.
1213    MultiviewerItcMode: u8 {
1214        /// The device reports no IT-content mode.
1215        UNKNOWN = 0;
1216        /// Video content.
1217        VIDEO = 1;
1218        /// PC content.
1219        PC = 2;
1220    }
1221}
1222
1223wire_enum! {
1224    /// The EDID template a multiviewer presents to its sources.
1225    ///
1226    /// A template's name is the largest resolution it advertises and the audio
1227    /// format it declares support for.
1228    MultiviewerEdidTemplate: u8 {
1229        /// The device reports no template.
1230        EDID_UNKNOWN = 0;
1231        /// 4K2K60 4:4:4, stereo 2.0.
1232        EDID_4K2K60_444_STEREO = 1;
1233        /// 4K2K60 4:4:4, Dolby/DTS 5.1.
1234        EDID_4K2K60_444_DOLBY_DTS_51 = 2;
1235        /// 4K2K60 4:4:4, HD audio 7.1.
1236        EDID_4K2K60_444_HD_AUDIO_71 = 3;
1237        /// 4K2K30 4:4:4, stereo 2.0.
1238        EDID_4K2K30_444_STEREO = 4;
1239        /// 4K2K30 4:4:4, Dolby/DTS 5.1.
1240        EDID_4K2K30_444_DOLBY_DTS_51 = 5;
1241        /// 4K2K30 4:4:4, HD audio 7.1.
1242        EDID_4K2K30_444_HD_AUDIO_71 = 6;
1243        /// 1080p, stereo 2.0.
1244        EDID_1080P_STEREO = 7;
1245        /// 1080p, Dolby/DTS 5.1.
1246        EDID_1080P_DOLBY_DTS_51 = 8;
1247        /// 1080p, HD audio 7.1.
1248        EDID_1080P_HD_AUDIO_71 = 9;
1249        /// 1920x1200, stereo 2.0.
1250        EDID_1920X1200_STEREO = 10;
1251        /// 1680x1050, stereo 2.0.
1252        EDID_1680X1050_STEREO = 11;
1253        /// 1600x1200, stereo 2.0.
1254        EDID_1600X1200_STEREO = 12;
1255        /// 1440x900, stereo 2.0.
1256        EDID_1440X900_STEREO = 13;
1257        /// 1360x768, stereo 2.0.
1258        EDID_1360X768_STEREO = 14;
1259        /// 1280x1024, stereo 2.0.
1260        EDID_1280X1024_STEREO = 15;
1261        /// 1024x768, stereo 2.0.
1262        EDID_1024X768_STEREO = 16;
1263        /// 720p, stereo 2.0.
1264        EDID_720P_STEREO = 17;
1265        /// Whatever the display connected to the HDMI output presents. The
1266        /// template a multiviewer leaves the factory with.
1267        EDID_COPY_OUTPUT = 18;
1268        /// The EDID loaded onto the device.
1269        EDID_CUSTOM = 19;
1270    }
1271}
1272
1273wire_enum! {
1274    /// The aspect ratio a multiviewer scales its windows to.
1275    MultiviewerAspectRatio: u8 {
1276        /// The device reports no aspect ratio.
1277        UNKNOWN = 0;
1278        /// Fill the window.
1279        FULL = 1;
1280        /// 16:9.
1281        RATIO_16_9 = 2;
1282    }
1283}
1284
1285wire_enum! {
1286    /// A multiviewer setting that is on, off, or not reported.
1287    MultiviewerBool: u8 {
1288        /// Off.
1289        OFF = 0;
1290        /// On.
1291        ON = 1;
1292        /// The device reports no value.
1293        UNKNOWN = 0xFF;
1294    }
1295}
1296
1297wire_enum! {
1298    /// One of a multiviewer's four inputs.
1299    ///
1300    /// The wire numbers the inputs from zero and this type from one, so that
1301    /// zero can mean "not reported" the way it does for every other
1302    /// multiviewer setting. So `to_wire` and `from_wire` carry this type's
1303    /// numbering rather than the wire's, and neither is the conversion to
1304    /// reach for when a raw multiviewer byte is what is in hand.
1305    MultiviewerSource: u8 {
1306        /// The device reports no source.
1307        UNKNOWN = 0;
1308        /// Input 1.
1309        INPUT_1 = 1;
1310        /// Input 2.
1311        INPUT_2 = 2;
1312        /// Input 3.
1313        INPUT_3 = 3;
1314        /// Input 4.
1315        INPUT_4 = 4;
1316    }
1317}
1318
1319impl MultiviewerSource {
1320    /// Reads a zero-based wire value, mapping anything past input 4 to
1321    /// [`MultiviewerSource::UNKNOWN`].
1322    ///
1323    /// The firmware spells "not known" as 0xFF, which lands past input 4 and
1324    /// so needs no case of its own.
1325    pub(crate) const fn from_zero_based(value: u8) -> Self {
1326        if value > 3 {
1327            Self::UNKNOWN
1328        } else {
1329            Self(value + 1)
1330        }
1331    }
1332
1333    /// The zero-based value the wire carries, or `None` for a source naming no
1334    /// input.
1335    ///
1336    /// A multiviewer reads zero as its first input, so there is no value that
1337    /// says "leave this alone": a request that cannot name an input has to be
1338    /// refused rather than sent.
1339    pub(crate) const fn to_zero_based(self) -> Option<u8> {
1340        match self.0 {
1341            1..=4 => Some(self.0 - 1),
1342            _ => None,
1343        }
1344    }
1345}
1346
1347impl MultiviewerBool {
1348    /// Reads a wire value, mapping anything but 0 and 1 to
1349    /// [`MultiviewerBool::UNKNOWN`].
1350    pub(crate) const fn from_wire_tristate(value: u8) -> Self {
1351        if value > 1 {
1352            Self::UNKNOWN
1353        } else {
1354            Self(value)
1355        }
1356    }
1357}
1358
1359wire_enum! {
1360    /// The colour space a OneIP output scales to.
1361    ///
1362    /// The field is four bits wide and only these four values are defined. A
1363    /// receiver passes the whole nibble to its validator, so a fifth value is
1364    /// dropped without a word rather than clamped to one of these.
1365    V2ipColourSpace: u8 {
1366        /// RGB.
1367        RGB = 0;
1368        /// YCbCr 4:4:4.
1369        YCBCR444 = 1;
1370        /// YCbCr 4:2:2.
1371        YCBCR422 = 2;
1372        /// YCbCr 4:2:0.
1373        YCBCR420 = 3;
1374    }
1375}
1376
1377wire_enum! {
1378    /// The 2-byte `mxr_signal_type` carried in scaling configs and bay signal
1379    /// reports.
1380    ///
1381    /// Byte 0 is the CTA-861 short video descriptor, 0 when the signal is not
1382    /// HDMI. Byte 1 packs `color:4` in the low nibble, then `non_int:1` and
1383    /// `bpp:3` above it.
1384    MxrSignalType: u16 {
1385        /// No signal format was reported.
1386        NONE = 0;
1387    }
1388}
1389
1390/// The bpp index a sender writes when it has no bit depth to report.
1391///
1392/// It sits outside the four indices that name a real depth, so an unset
1393/// format reads differently from every genuine one.
1394const SIG_BPP_UNSET: u16 = 5;
1395
1396impl MxrSignalType {
1397    /// The CTA-861 short video descriptor, 0 when the signal is not HDMI.
1398    pub const fn svd(self) -> u8 {
1399        (self.0 & 0xFF) as u8
1400    }
1401
1402    /// The colour space.
1403    pub const fn colour_space(self) -> u8 {
1404        ((self.0 >> 8) & 0xF) as u8
1405    }
1406
1407    /// Whether the frame rate carries a 1000/1001 clock.
1408    pub const fn is_non_integer(self) -> bool {
1409        self.0 & (1 << 12) != 0
1410    }
1411
1412    /// The raw bpp index as carried on the wire. The field is an index, not a
1413    /// bit depth; [`MxrSignalType::bpp`] converts it.
1414    pub const fn bpp_index(self) -> u8 {
1415        ((self.0 >> 13) & 0x7) as u8
1416    }
1417
1418    /// The bit depth the bpp index stands for, `None` when unknown or unset.
1419    pub const fn bpp(self) -> Option<u8> {
1420        match self.bpp_index() {
1421            1 => Some(8),
1422            2 => Some(10),
1423            3 => Some(12),
1424            4 => Some(16),
1425            _ => None,
1426        }
1427    }
1428
1429    /// Reports whether the word carries a signal format at all.
1430    ///
1431    /// A bay with nothing configured says so two ways. A sender that zeroes
1432    /// the word and stamps the unset bpp index leaves an index no real depth
1433    /// uses, and one that writes a plain zero leaves nothing at all. Neither
1434    /// is a format, and the svd and colour space beside them are not answers
1435    /// either: both read as zero, which is what this word says for "not HDMI"
1436    /// and "RGB" when it *is* set.
1437    pub const fn is_set(self) -> bool {
1438        self.0 != 0 && self.bpp_index() as u16 != SIG_BPP_UNSET
1439    }
1440
1441    /// Builds the word from the fields a scaling write consumes.
1442    ///
1443    /// `non_int` is left clear: the receiving struct carries the bit, and the
1444    /// apply path does not read it.
1445    ///
1446    /// Building rather than editing is the point. A sink with no mode
1447    /// configured reports the word with the unset bpp index in it, so a caller
1448    /// that read that word back and filled in an svd would send an index no
1449    /// depth uses - which the receiver decodes to zero and rejects without
1450    /// answering.
1451    pub(crate) const fn from_parts(svd: u8, colour: u8, bpp_index: u8) -> Self {
1452        Self((svd as u16) | (((colour & 0xF) as u16) << 8) | (((bpp_index & 0x7) as u16) << 13))
1453    }
1454
1455    /// The bpp index that stands for a bit depth, `None` for a depth no index
1456    /// names.
1457    ///
1458    /// Only the three depths a OneIP output stage accepts are here. Index 4
1459    /// names 16bpp, which [`MxrSignalType::bpp`] reads back from a device, but
1460    /// the output stage refuses it - so offering it as something to write would
1461    /// send a frame that is decoded cleanly and then dropped in silence.
1462    pub(crate) const fn bpp_index_for_depth(depth: u8) -> Option<u8> {
1463        match depth {
1464            8 => Some(1),
1465            10 => Some(2),
1466            12 => Some(3),
1467            _ => None,
1468        }
1469    }
1470}
1471
1472impl fmt::Display for MxrSignalType {
1473    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1474        if !self.is_set() {
1475            return f.write_str("unset");
1476        }
1477        match self.bpp() {
1478            Some(bpp) => write!(
1479                f,
1480                "svd {}, color {}, {}bpp",
1481                self.svd(),
1482                self.colour_space(),
1483                bpp
1484            ),
1485            None => write!(f, "svd {}, color {}", self.svd(), self.colour_space()),
1486        }
1487    }
1488}
1489
1490bitmask! {
1491    /// The kinds of overlay setting an overlay sync copies.
1492    OsdSyncWhat: u8 {
1493        /// Captions.
1494        CAPTIONS = 1 << 0;
1495        /// Which logo is shown, and where.
1496        LOGO = 1 << 1;
1497        /// Background colours.
1498        BACKGROUNDS = 1 << 2;
1499        /// The logo images in the four slots.
1500        LOGO_SLOTS = 1 << 3;
1501    }
1502}
1503
1504bitmask! {
1505    /// What a unit says of the message on its output.
1506    OsdMessageFlag: u8 {
1507        /// A message is set. In a write: show it, rather than take it down.
1508        SET = 1 << 0;
1509        /// The output shows it, rather than another message ahead of it.
1510        SHOWN = 1 << 1;
1511        /// The unit's video processor composes the message layer.
1512        SUPPORTED = 1 << 2;
1513        /// The unit did not take the write it answers, and reports the
1514        /// message it kept.
1515        REFUSED = 1 << 3;
1516    }
1517}
1518
1519wire_enum! {
1520    /// Where a message stands against the unit's own messages: its upgrade,
1521    /// Locate Device and encrypted source messages.
1522    OsdMessagePriority: u8 {
1523        /// After them.
1524        LOW = 0;
1525        /// Between them.
1526        NORMAL = 1;
1527        /// Before them.
1528        TOP = 2;
1529    }
1530}
1531
1532wire_enum! {
1533    /// How far a unit has got with taking an overlay sync.
1534    OsdSyncState: u8 {
1535        /// Taking it.
1536        TAKING = 0;
1537        /// Every setting written and committed.
1538        DONE = 1;
1539        /// Failed, for the reason the report gives.
1540        FAILED = 2;
1541        /// Kept for later, as the unit's video processor is off in power
1542        /// save.
1543        DEFERRED = 3;
1544    }
1545}
1546
1547wire_enum! {
1548    /// The output size an overlay setting applies at.
1549    OsdSyncSize: u8 {
1550        /// Large outputs, which every value but `SMALL` also means.
1551        LARGE = 1;
1552        /// Small outputs.
1553        SMALL = 2;
1554    }
1555}
1556
1557wire_enum! {
1558    /// How a caption's lines are aligned.
1559    OsdCaptionAlign: u8 {
1560        /// To the left.
1561        LEFT = 0;
1562        /// Centred.
1563        CENTRE = 1;
1564        /// To the right.
1565        RIGHT = 2;
1566        /// The layout's default.
1567        DEFAULT = 0xFF;
1568    }
1569}
1570
1571wire_enum! {
1572    /// How a caption's text is read.
1573    OsdCaptionFormat: u8 {
1574        /// As plain text.
1575        PLAIN = 0;
1576        /// As Markdown.
1577        MARKDOWN = 1;
1578        /// The layout's default.
1579        DEFAULT = 0xFF;
1580    }
1581}
1582
1583bitmask! {
1584    /// The caption fields an overlay setting gives. A field left out keeps
1585    /// what the unit has.
1586    OsdCaptionField: u16 {
1587        /// The font.
1588        FONT = 1 << 0;
1589        /// The size.
1590        SIZE = 1 << 1;
1591        /// The horizontal position.
1592        X = 1 << 2;
1593        /// The vertical position.
1594        Y = 1 << 3;
1595        /// The alignment.
1596        ALIGN = 1 << 4;
1597        /// The format.
1598        FORMAT = 1 << 5;
1599        /// The colour.
1600        COLOUR = 1 << 6;
1601        /// The wrap width.
1602        WRAP = 1 << 7;
1603        /// The text.
1604        TEXT = 1 << 8;
1605    }
1606}
1607
1608bitmask! {
1609    /// The logo fields an overlay setting gives. A field left out keeps what
1610    /// the unit has.
1611    OsdLogoField: u16 {
1612        /// Which logo.
1613        LOGO = 1 << 0;
1614        /// The horizontal position.
1615        X = 1 << 1;
1616        /// The vertical position.
1617        Y = 1 << 2;
1618    }
1619}
1620
1621bitmask! {
1622    /// The first byte of a third-party vendor id, ahead of its three
1623    /// identifying bytes.
1624    ///
1625    /// Bits without a name are passed through as they arrived.
1626    ThirdPartyVendorFlag: u8 {
1627        /// The three bytes are an identifier the vendor chose, rather than its
1628        /// IEEE OUI.
1629        NOT_OUI = 1 << 0;
1630    }
1631}