Skip to main content

rcvbp/
record01.rs

1//! Record 0x01, the main receiver-parameter record: named byte offsets and
2//! typed accessors. The field dictionary is `docs/record-0x01-fields.md`.
3
4use anyhow::{bail, Result};
5
6/// Payload length of record 0x01.
7pub const LEN: usize = panelspec::RECORD01_LEN;
8
9/// Byte offsets into the record payload.
10pub mod off {
11    pub const MODULE_W: usize = 0x000;
12    pub const MODULE_H_HALF: usize = 0x001;
13    pub const GAMMA: usize = 0x01C;
14    pub const SCAN: usize = 0x020;
15    pub const SERIAL_CLOCK: usize = 0x021;
16    pub const GRAY: usize = 0x023;
17    pub const LUMINANCE: usize = 0x024;
18    pub const LUMINANCE_LEVEL: usize = 0x026;
19    /// Three bytes copied into the basic pack head (`ff ff ff` in practice).
20    pub const PACK_HEAD3: usize = 0x028;
21    pub const COLOR_SWAP: usize = 0x02B;
22    pub const COLOR_SOURCE: usize = 0x02C;
23    pub const GCLOCK: usize = 0x031;
24    pub const GAINS: usize = 0x032;
25    pub const CHIP_LO: usize = 0x036;
26    pub const LINE_DIR: usize = 0x03C;
27    pub const SPLIT: usize = 0x03E;
28    pub const DATA_GROUPS: usize = 0x044;
29    pub const SERIAL_CLOCK_HALF: usize = 0x049;
30    pub const SERIAL_CLOCK_DUP: usize = 0x04B;
31    /// Bit 0: 8 ns OE.
32    pub const OE_8NS: usize = 0x050;
33    pub const GRID_W_LO: usize = 0x057;
34    pub const GRID_H_LO: usize = 0x058;
35    pub const HR_STYLE: usize = 0x059;
36    pub const CHIP_CUSTOM: usize = 0x06A;
37    pub const REFRESH: usize = 0x0AA;
38    pub const MIN_OE: usize = 0x0AE;
39    pub const HR_SCAN_STYLE: usize = 0x0B2;
40    pub const CURRENT_PCT: usize = 0x0B4;
41    pub const MAX_W: usize = 0x0C0;
42    pub const MAX_H: usize = 0x0C2;
43    pub const SEGMENTS_MINUS_1: usize = 0x0E5;
44    /// Secondary (sub-variant) chip id, low byte; high byte at +0x205.
45    pub const SUB_CHIP_LO: usize = 0x0E9;
46    pub const SUB_CHIP_HI: usize = 0x205;
47    pub const SWAP_RAMP: usize = 0x19A;
48    pub const CHIP_HI: usize = 0x204;
49    pub const GRID_W_HI: usize = 0x24E;
50    pub const GRID_H_HI: usize = 0x24F;
51}
52
53/// Read-only typed view over a record 0x01 payload.
54#[derive(Clone, Copy)]
55pub struct View<'a>(&'a [u8]);
56
57impl<'a> View<'a> {
58    /// # Errors
59    /// Rejects a payload shorter than the record.
60    pub fn new(payload: &'a [u8]) -> Result<Self> {
61        if payload.len() < LEN {
62            bail!("record 0x01 payload is {} bytes, need {LEN}", payload.len());
63        }
64        Ok(Self(payload))
65    }
66
67    #[must_use]
68    pub fn bytes(&self) -> &'a [u8] {
69        self.0
70    }
71    #[must_use]
72    pub fn u8(&self, at: usize) -> u8 {
73        self.0[at]
74    }
75    #[must_use]
76    pub fn u16_le(&self, at: usize) -> u16 {
77        u16::from_le_bytes([self.0[at], self.0[at + 1]])
78    }
79    #[must_use]
80    pub fn f32_le(&self, at: usize) -> f32 {
81        f32::from_le_bytes([self.0[at], self.0[at + 1], self.0[at + 2], self.0[at + 3]])
82    }
83
84    #[must_use]
85    pub fn module_width(&self) -> u16 {
86        u16::from(self.u8(off::MODULE_W))
87    }
88    /// The record stores half the module height.
89    #[must_use]
90    pub fn module_height_stored(&self) -> u16 {
91        u16::from(self.u8(off::MODULE_H_HALF))
92    }
93    #[must_use]
94    pub fn scan(&self) -> u8 {
95        self.u8(off::SCAN)
96    }
97    #[must_use]
98    pub fn serial_clock(&self) -> u16 {
99        self.u16_le(off::SERIAL_CLOCK)
100    }
101    #[must_use]
102    pub fn gray(&self) -> u8 {
103        self.u8(off::GRAY)
104    }
105    #[must_use]
106    pub fn luminance_level(&self) -> u16 {
107        self.u16_le(off::LUMINANCE_LEVEL)
108    }
109    #[must_use]
110    pub fn max_width(&self) -> u16 {
111        self.u16_le(off::MAX_W)
112    }
113    #[must_use]
114    pub fn max_height(&self) -> u16 {
115        self.u16_le(off::MAX_H)
116    }
117    /// The module-position grid unit (16x16 in practice), split across two
118    /// byte pairs in the record.
119    #[must_use]
120    pub fn grid(&self) -> (u16, u16) {
121        (
122            u16::from_le_bytes([self.u8(off::GRID_W_LO), self.u8(off::GRID_W_HI)]),
123            u16::from_le_bytes([self.u8(off::GRID_H_LO), self.u8(off::GRID_H_HI)]),
124        )
125    }
126    /// Data-line direction: 0/1 vertical, 2/3 horizontal.
127    #[must_use]
128    pub fn line_dir(&self) -> u8 {
129        self.u8(off::LINE_DIR)
130    }
131    /// Vendor `GetSplitSegment` from the +0x03E code.
132    #[must_use]
133    pub fn split_segment(&self) -> u8 {
134        let c = self.u8(off::SPLIT);
135        if c & 4 != 0 {
136            4
137        } else if c & 1 == 0 {
138            1
139        } else if c < 8 {
140            2
141        } else {
142            c >> 3
143        }
144    }
145    /// PWM segment count for the scan-table solver (+0x0E5 + 1).
146    #[must_use]
147    pub fn segments(&self) -> u32 {
148        u32::from(self.u8(off::SEGMENTS_MINUS_1)) + 1
149    }
150    #[must_use]
151    pub fn min_oe(&self) -> f32 {
152        self.f32_le(off::MIN_OE)
153    }
154    /// High-refresh style with the vendor's runtime bit masked off.
155    #[must_use]
156    pub fn hr_style(&self) -> u8 {
157        self.u8(off::HR_STYLE) & !0x20
158    }
159    #[must_use]
160    pub fn hr_scan_style(&self) -> u8 {
161        self.u8(off::HR_SCAN_STYLE)
162    }
163    #[must_use]
164    pub fn swap_ramp(&self) -> &'a [u8] {
165        &self.0[off::SWAP_RAMP..off::SWAP_RAMP + 64]
166    }
167    #[must_use]
168    pub fn chip_custom(&self) -> &'a [u8] {
169        &self.0[off::CHIP_CUSTOM..off::CHIP_CUSTOM + 16]
170    }
171    #[must_use]
172    pub fn chip_id(&self) -> u16 {
173        u16::from(self.u8(off::CHIP_HI)) << 8 | u16::from(self.u8(off::CHIP_LO))
174    }
175}