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