waverave_hackrf/
info.rs

1//! Get information about a HackRF board.
2//!
3//! This module contains the [`Info`] struct for accessing information from the
4//! HackRF, which can be used to get:
5//!
6//! - The MCU's [serial number][SerialNumber] with [Info::serial].
7//! - The ["compatible" platforms][SupportedPlatform] for a given board, with [Info::supported_platform]
8//! - The [board identifier][BoardId], with [Info::board_id]
9//! - The [board revision][BoardRev], with [Info::board_rev]
10//!
11//! The general way to do this with a HackRF is:
12//!
13//! ```no_run
14//!
15//! # use anyhow::Result;
16//! # #[tokio::main]
17//! # async fn main() -> Result<()> {
18//!
19//! use waverave_hackrf::info::*;
20//!
21//! let hackrf = waverave_hackrf::open_hackrf()?;
22//! let info = hackrf.info();
23//!
24//! let serial: SerialNumber = info.serial().await?;
25//! let compatible: SupportedPlatform = info.supported_platform().await?;
26//! let board_id: BoardId = info.board_id().await?;
27//! let board_rev: BoardRev = info.board_rev().await?;
28//!
29//! # Ok(())
30//! # }
31//! ```
32use crate::{Error, HackRf, HackRfType, consts::ControlRequest};
33
34/// The MCU serial number.
35///
36/// The Part ID identifies the exact LPC43xx part that was populated. See the
37/// user manual for the exact decoding, but you're likely to find `0xa000cb3c`
38/// for `part_id[0]`.
39///
40/// The "serial number" is referred to as the device unique ID in the user
41/// manual for the LPC43x. It seems that only the last two 32-bit words are
42/// nonzero, though this isn't guaranteed.
43///
44/// See the LPC43xx documentation for full details.
45#[allow(missing_docs)]
46#[repr(C)]
47#[derive(Clone, Copy, Debug, bytemuck::Zeroable, bytemuck::Pod)]
48pub struct SerialNumber {
49    pub part_id: [u32; 2],
50    pub serial_no: [u32; 4],
51}
52
53impl SerialNumber {
54    fn le_convert(&mut self) {
55        for x in self.part_id.iter_mut() {
56            *x = x.to_le();
57        }
58        for x in self.serial_no.iter_mut() {
59            *x = x.to_le();
60        }
61    }
62}
63
64/// The board revision.
65///
66/// The Great Scott Gadgets official board revisions are prefixed with "Gsg".
67#[allow(missing_docs)]
68#[derive(Clone, Copy, Debug)]
69pub enum BoardRev {
70    Old,
71    R6,
72    R7,
73    R8,
74    R9,
75    R10,
76    GsgR6,
77    GsgR7,
78    GsgR8,
79    GsgR9,
80    GsgR10,
81    Unknown(u8),
82}
83
84impl std::fmt::Display for BoardRev {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        match self {
87            Self::Old => f.write_str("Older than r6"),
88            Self::R6 => f.write_str("r6"),
89            Self::R7 => f.write_str("r7"),
90            Self::R8 => f.write_str("r8"),
91            Self::R9 => f.write_str("r9"),
92            Self::R10 => f.write_str("r10"),
93            Self::GsgR6 => f.write_str("Great Scott Gadgets r6"),
94            Self::GsgR7 => f.write_str("Great Scott Gadgets r7"),
95            Self::GsgR8 => f.write_str("Great Scott Gadgets r8"),
96            Self::GsgR9 => f.write_str("Great Scott Gadgets r9"),
97            Self::GsgR10 => f.write_str("Great Scott Gadgets r10"),
98            Self::Unknown(v) => write!(f, "unknown (0x{v:x})"),
99        }
100    }
101}
102
103impl BoardRev {
104    /// Check if this is marked as an official Great Scott Gadgets board.
105    pub fn is_official(&self) -> bool {
106        use BoardRev::*;
107        matches!(self, GsgR6 | GsgR7 | GsgR8 | GsgR9 | GsgR10)
108    }
109
110    fn from_u8(v: u8) -> Self {
111        use BoardRev::*;
112        match v {
113            0 => Old,
114            1 => R6,
115            2 => R7,
116            3 => R8,
117            4 => R9,
118            5 => R10,
119            0x81 => GsgR6,
120            0x82 => GsgR7,
121            0x83 => GsgR8,
122            0x84 => GsgR9,
123            0x85 => GsgR10,
124            v => Unknown(v),
125        }
126    }
127}
128
129/// The physical board's identifier. These differentiate between board hardware
130/// that's actually different.
131#[allow(missing_docs)]
132#[derive(Clone, Copy, Debug)]
133pub enum BoardId {
134    Jellybean,
135    Jawbreaker,
136    HackRf1Og,
137    Rad1o,
138    HackRf1R9,
139    Unknown(u8),
140}
141
142impl std::fmt::Display for BoardId {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        match self {
145            Self::Jellybean => f.write_str("Jellybean"),
146            Self::Jawbreaker => f.write_str("Jawbreaker"),
147            Self::HackRf1Og => f.write_str("HackRF One"),
148            Self::Rad1o => f.write_str("rad1o"),
149            Self::HackRf1R9 => f.write_str("HackRF One Rev9"),
150            Self::Unknown(v) => write!(f, "Unknown (0x{v:x})"),
151        }
152    }
153}
154
155impl BoardId {
156    fn from_u8(v: u8) -> Self {
157        use BoardId::*;
158        match v {
159            0 => Jellybean,
160            1 => Jawbreaker,
161            2 => HackRf1Og,
162            3 => Rad1o,
163            4 => HackRf1R9,
164            v => Unknown(v),
165        }
166    }
167}
168
169/// Compatible platforms for this board.
170#[allow(missing_docs)]
171#[derive(Clone, Copy, Debug)]
172pub struct SupportedPlatform {
173    pub jawbreaker: bool,
174    pub hackrf1_og: bool,
175    pub rad1o: bool,
176    pub hackrf1_r9: bool,
177}
178
179impl SupportedPlatform {
180    fn from_u32(v: u32) -> Self {
181        Self {
182            jawbreaker: v & 1 != 0,
183            hackrf1_og: v & 2 != 0,
184            rad1o: v & 4 != 0,
185            hackrf1_r9: v & 8 != 0,
186        }
187    }
188}
189
190/// Info-gathering operations for the HackRF.
191///
192/// Borrows the interface while doing operations.
193pub struct Info<'a> {
194    inner: &'a HackRf,
195}
196
197impl<'a> Info<'a> {
198    pub(crate) fn new(inner: &'a HackRf) -> Info<'a> {
199        Self { inner }
200    }
201
202    /// Get the device's implemented API version, as a binary-coded decimal
203    /// (BCD) value.
204    pub fn api_version(&self) -> u16 {
205        self.inner.version
206    }
207
208    /// Get the [type][HackRfType] of HackRF radio.
209    pub fn radio_type(&self) -> HackRfType {
210        self.inner.ty
211    }
212
213    /// Get the [board hardware ID][BoardId].
214    pub async fn board_id(&self) -> Result<BoardId, Error> {
215        let ret = self.inner.read_u8(ControlRequest::BoardIdRead, 0).await?;
216        Ok(BoardId::from_u8(ret))
217    }
218
219    /// Get the firmware version as a string.
220    pub async fn version_string(&self) -> Result<String, Error> {
221        let resp = self
222            .inner
223            .read_bytes(ControlRequest::VersionStringRead, 255)
224            .await?;
225        String::from_utf8(resp).map_err(|_| Error::ReturnData)
226    }
227
228    /// Get the MCU's serial numbers.
229    ///
230    /// In the LP43xx documentation, this refers to the device unique ID and the
231    /// part identification number.
232    ///
233    /// See [`SerialNumber`] for more info.
234    pub async fn serial(&self) -> Result<SerialNumber, Error> {
235        let mut v: SerialNumber = self
236            .inner
237            .read_struct(ControlRequest::BoardPartidSerialnoRead)
238            .await?;
239        v.le_convert();
240        Ok(v)
241    }
242
243    /// Read the board's [revision number][BoardRev].
244    ///
245    /// Requires API version 0x0106 or higher.
246    pub async fn board_rev(&self) -> Result<BoardRev, Error> {
247        self.inner.api_check(0x0106)?;
248        let rev = self.inner.read_u8(ControlRequest::BoardRevRead, 0).await?;
249        Ok(BoardRev::from_u8(rev))
250    }
251
252    /// Read the platforms [compatible][SupportedPlatform] with this board.
253    ///
254    /// Requires API version 0x0106 or higher.
255    pub async fn supported_platform(&self) -> Result<SupportedPlatform, Error> {
256        self.inner.api_check(0x0106)?;
257        let ret = self
258            .inner
259            .read_bytes(ControlRequest::SupportedPlatformRead, 4)
260            .await?;
261        let ret: [u8; 4] = ret.as_slice().try_into().map_err(|_| Error::ReturnData)?;
262        let val = u32::from_be_bytes(ret);
263        Ok(SupportedPlatform::from_u32(val))
264    }
265}