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}