kenwood_thd75/types/
band.rs

1//! Band selection for the TH-D75 transceiver.
2
3use std::fmt;
4
5use crate::error::ValidationError;
6
7/// Radio band index (0-13).
8///
9/// The TH-D75 uses a numeric band index in the `FO` and `ME` commands.
10/// Variants `A` and `B` correspond to the two main VFO bands; the
11/// remaining `Band2`..`Band13` map to additional sub-band selections.
12///
13/// # Band architecture (per Kenwood Operating Tips §1.1, §5.9; User Manual Chapter 5)
14///
15/// - **Band A** (upper display): Amateur-only TX/RX at 144 MHz, 220 MHz
16///   (TH-D75A only), and 430 MHz. Supports FM and DV modes.
17///   Pressing and holding `[Left]/[Right]` cycles: 144 <-> 220 <-> 430 MHz.
18///   Band A uses a double super heterodyne receiver (1st IF 57.15 MHz,
19///   2nd IF 450 kHz) with VCO/PLL IC800 and IF IC IC900 (AK2365AU).
20/// - **Band B** (lower display): Wideband RX from 0.1-524 MHz. Supports
21///   FM, DV, AM, LSB, USB, CW, NFM, WFM (FM Radio mode only), and DR.
22///   Band B has an independent receiver chain with its own VCO/PLL IC700,
23///   IF IC IC1002 (AK2365AU), and a third IF stage at 10.8 kHz via 3rd
24///   mixer IC1001 for AM/SSB/CW demodulation. 1st IF is 58.05 MHz, 2nd
25///   IF is 450 kHz. This independent hardware allows both bands to
26///   receive simultaneously.
27///   Pressing and holding `[Left]/[Right]` cycles: 430 <-> UHF(470-524) <->
28///   LF/MF(0.1-1.71) <-> HF(1.71-29.7) <-> 50(29.7-76) <-> FMBC(76-108) <->
29///   118(108-136) <-> 144(136-174) <-> VHF(174-216/230) <-> 200/300(216/230-410) <-> 430 MHz.
30///
31/// Both bands share the MAIN MPU (IC2005, OMAP-L138), CODEC (IC2011),
32/// and SUB MPU (IC1103) which controls the VCO/PLLs and IF ICs via SPI.
33/// The VCO/PLL reference clocks are TCXO1 57.6 MHz (X600) and TCXO2
34/// 55.95 MHz (X601), selected by analog switches IC604/IC605.
35///
36/// Per service manual §2.1.5, the Band B VCO/PLL (IC700) is also used
37/// for transmission on all bands. Band A's VCO/PLL (IC800) handles
38/// Band A 1st local oscillation only.
39///
40/// # Hardware signal path (per service manual §2.1.3, §2.1.4)
41///
42/// ```text
43/// Band A RX: ANT → LNA Q404/Q406 → BPF → 1st MIX Q400 → IF 57.15MHz
44///            → MCF XF1 → IF AMP Q900 → IC900 → 2nd IF 450kHz → CODEC IC2011
45///
46/// Band B RX: ANT → LNA Q404/Q406 → BPF → 1st MIX Q500 → IF 58.05MHz
47///            → MCF XF2 → IF AMP Q1000 → IC1002 → 2nd IF 450kHz → CODEC IC2011
48///            (AM/SSB/CW: → 3rd MIX IC1001 → 3rd IF 10.8kHz → CODEC)
49///
50/// TX (all):  CODEC IC2011 → MOD AMP IC2027 → SUB MPU IC1103 → Band B
51///            VCO/PLL IC700 → RF AMP Q201 → PRE DRV IC201 → DRV AMP Q212
52///            → FINAL AMP Q217/Q218 → ANT
53/// ```
54///
55/// Band A is the CTRL/PTT band by default. Band B supports all
56/// demodulation modes including SSB/CW with DSP and an IF receive filter.
57///
58/// # Dual/Single band display (per User Manual Chapter 5)
59///
60/// Press `[F]`, `[A/B]` to toggle between dual-band (both A and B visible)
61/// and single-band (only the selected band visible) display modes.
62///
63/// # Two-wave simultaneous reception (per User Manual Chapter 2)
64///
65/// Supported band combinations: `VxU`, `UxV`, `UxU` (both models), plus
66/// `Vx220M`, `220MxV`, `Ux220M` (TH-D75A only). D-STAR 2-wave simultaneous
67/// reception is also supported.
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub enum Band {
70    /// Band A — amateur TX/RX (144/220/430 MHz). Index 0.
71    A = 0,
72    /// Band B — wideband RX (0.1–524 MHz, all modes). Index 1.
73    B = 1,
74    /// Band 2 (index 2). Extended band index used internally by the firmware
75    /// for multi-band selection. Most CAT commands (e.g., `FQ`, `MD`, `SQ`)
76    /// only accept Band A (0) or Band B (1); sending an extended index
77    /// typically results in a `?` error response.
78    Band2 = 2,
79    /// Band 3 (index 3). Extended firmware band index — see [`Band::Band2`]
80    /// for details on CAT command restrictions.
81    Band3 = 3,
82    /// Band 4 (index 4). Extended firmware band index — see [`Band::Band2`]
83    /// for details on CAT command restrictions.
84    Band4 = 4,
85    /// Band 5 (index 5). Extended firmware band index — see [`Band::Band2`]
86    /// for details on CAT command restrictions.
87    Band5 = 5,
88    /// Band 6 (index 6). Extended firmware band index — see [`Band::Band2`]
89    /// for details on CAT command restrictions.
90    Band6 = 6,
91    /// Band 7 (index 7). Extended firmware band index — see [`Band::Band2`]
92    /// for details on CAT command restrictions.
93    Band7 = 7,
94    /// Band 8 (index 8). Extended firmware band index — see [`Band::Band2`]
95    /// for details on CAT command restrictions.
96    Band8 = 8,
97    /// Band 9 (index 9). Extended firmware band index — see [`Band::Band2`]
98    /// for details on CAT command restrictions.
99    Band9 = 9,
100    /// Band 10 (index 10). Extended firmware band index — see [`Band::Band2`]
101    /// for details on CAT command restrictions.
102    Band10 = 10,
103    /// Band 11 (index 11). Extended firmware band index — see [`Band::Band2`]
104    /// for details on CAT command restrictions.
105    Band11 = 11,
106    /// Band 12 (index 12). Extended firmware band index — see [`Band::Band2`]
107    /// for details on CAT command restrictions.
108    Band12 = 12,
109    /// Band 13 (index 13). Extended firmware band index — see [`Band::Band2`]
110    /// for details on CAT command restrictions.
111    Band13 = 13,
112}
113
114impl Band {
115    /// Number of valid band values (0-13).
116    pub const COUNT: u8 = 14;
117}
118
119impl fmt::Display for Band {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        match self {
122            Self::A => f.write_str("A"),
123            Self::B => f.write_str("B"),
124            other => write!(f, "Band {}", u8::from(*other)),
125        }
126    }
127}
128
129impl TryFrom<u8> for Band {
130    type Error = ValidationError;
131
132    fn try_from(value: u8) -> Result<Self, Self::Error> {
133        match value {
134            0 => Ok(Self::A),
135            1 => Ok(Self::B),
136            2 => Ok(Self::Band2),
137            3 => Ok(Self::Band3),
138            4 => Ok(Self::Band4),
139            5 => Ok(Self::Band5),
140            6 => Ok(Self::Band6),
141            7 => Ok(Self::Band7),
142            8 => Ok(Self::Band8),
143            9 => Ok(Self::Band9),
144            10 => Ok(Self::Band10),
145            11 => Ok(Self::Band11),
146            12 => Ok(Self::Band12),
147            13 => Ok(Self::Band13),
148            _ => Err(ValidationError::BandOutOfRange(value)),
149        }
150    }
151}
152
153impl From<Band> for u8 {
154    fn from(band: Band) -> Self {
155        band as Self
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use crate::error::ValidationError;
163
164    #[test]
165    fn band_valid_range() {
166        for i in 0u8..Band::COUNT {
167            assert!(Band::try_from(i).is_ok(), "Band({i}) should be valid");
168        }
169    }
170
171    #[test]
172    fn band_invalid() {
173        assert!(Band::try_from(Band::COUNT).is_err());
174        assert!(Band::try_from(255).is_err());
175    }
176
177    #[test]
178    fn band_round_trip() {
179        for i in 0u8..Band::COUNT {
180            let val = Band::try_from(i).unwrap();
181            assert_eq!(u8::from(val), i);
182        }
183    }
184
185    #[test]
186    fn band_error_variant() {
187        let err = Band::try_from(Band::COUNT).unwrap_err();
188        assert!(matches!(err, ValidationError::BandOutOfRange(14)));
189    }
190
191    #[test]
192    fn band_display() {
193        assert_eq!(Band::A.to_string(), "A");
194        assert_eq!(Band::B.to_string(), "B");
195        assert_eq!(Band::Band5.to_string(), "Band 5");
196        assert_eq!(Band::Band13.to_string(), "Band 13");
197    }
198}