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