uhf_rfid/
types.rs

1//! Types for RFID operations
2
3/// Information about a detected RFID tag
4#[derive(Debug, Clone)]
5pub struct TagInfo {
6    pub epc: String,
7    pub rssi: u8,
8}
9
10/// Memory bank selection for tag operations
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(u8)]
13pub enum MemoryBank {
14    /// Reserved memory bank (RFU)
15    Reserved = 0x00,
16    /// EPC memory bank
17    Epc = 0x01,
18    /// TID memory bank
19    Tid = 0x02,
20    /// User memory bank
21    User = 0x03,
22}
23
24/// Target flag for Select command (per EPC Gen2)
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26#[repr(u8)]
27pub enum SelectTarget {
28    /// Inventoried S0
29    #[default]
30    S0 = 0x00,
31    /// Inventoried S1
32    S1 = 0x01,
33    /// Inventoried S2
34    S2 = 0x02,
35    /// Inventoried S3
36    S3 = 0x03,
37    /// SL flag
38    Sl = 0x04,
39}
40
41/// Action for Select command (per EPC Gen2)
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43#[repr(u8)]
44pub enum SelectAction {
45    /// Match: assert SL or inventoried→A, Non-match: deassert SL or inventoried→B
46    #[default]
47    Action0 = 0x00,
48    /// Match: assert SL or inventoried→A, Non-match: do nothing
49    Action1 = 0x01,
50    /// Match: do nothing, Non-match: deassert SL or inventoried→B
51    Action2 = 0x02,
52    /// Match: negate SL or invert, Non-match: do nothing
53    Action3 = 0x03,
54    /// Match: deassert SL or inventoried→B, Non-match: assert SL or inventoried→A
55    Action4 = 0x04,
56    /// Match: deassert SL or inventoried→B, Non-match: do nothing
57    Action5 = 0x05,
58    /// Match: do nothing, Non-match: assert SL or inventoried→A
59    Action6 = 0x06,
60    /// Match: do nothing, Non-match: negate SL or invert
61    Action7 = 0x07,
62}
63
64/// Select mode configuration
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
66#[repr(u8)]
67pub enum SelectMode {
68    /// Send Select command before every tag operation
69    Always = 0x00,
70    /// Do not send Select command before tag operations
71    #[default]
72    Disabled = 0x01,
73    /// Send Select only before non-polling operations (Read, Write, Lock, Kill)
74    NonPolling = 0x02,
75}
76
77/// Parameters for the Select command
78#[derive(Debug, Clone)]
79pub struct SelectParams {
80    /// Target session/flag
81    pub target: SelectTarget,
82    /// Action to perform
83    pub action: SelectAction,
84    /// Memory bank to match against
85    pub mem_bank: MemoryBank,
86    /// Bit pointer (starting bit position in memory bank)
87    pub pointer: u32,
88    /// Mask data to match
89    pub mask: Vec<u8>,
90    /// Whether to enable truncation
91    pub truncate: bool,
92}
93
94/// Operating region for the reader
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96#[repr(u8)]
97pub enum Region {
98    /// China 900 MHz band
99    China900 = 0x01,
100    /// US band
101    Us = 0x02,
102    /// Europe band
103    Europe = 0x03,
104    /// China 800 MHz band
105    China800 = 0x04,
106    /// South Korea band
107    Korea = 0x06,
108}
109
110impl Region {
111    /// Get the base frequency for this region in MHz
112    pub fn base_frequency(&self) -> f64 {
113        match self {
114            Region::China900 => 920.125,
115            Region::Us => 902.25,
116            Region::Europe => 865.1,
117            Region::China800 => 840.125,
118            Region::Korea => 917.1,
119        }
120    }
121
122    /// Get the channel spacing for this region in MHz
123    pub fn channel_spacing(&self) -> f64 {
124        match self {
125            Region::China900 => 0.25,
126            Region::Us => 0.5,
127            Region::Europe => 0.2,
128            Region::China800 => 0.25,
129            Region::Korea => 0.2,
130        }
131    }
132
133    /// Calculate channel index from frequency
134    pub fn channel_from_frequency(&self, freq_mhz: f64) -> u8 {
135        ((freq_mhz - self.base_frequency()) / self.channel_spacing()) as u8
136    }
137
138    /// Calculate frequency from channel index
139    pub fn frequency_from_channel(&self, channel: u8) -> f64 {
140        (channel as f64) * self.channel_spacing() + self.base_frequency()
141    }
142}
143
144impl TryFrom<u8> for Region {
145    type Error = ();
146
147    fn try_from(value: u8) -> Result<Self, Self::Error> {
148        match value {
149            0x01 => Ok(Region::China900),
150            0x02 => Ok(Region::Us),
151            0x03 => Ok(Region::Europe),
152            0x04 => Ok(Region::China800),
153            0x06 => Ok(Region::Korea),
154            _ => Err(()),
155        }
156    }
157}
158
159/// Query parameters for tag inventory (per EPC Gen2)
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub struct QueryParams {
162    /// Sel field: which tags respond to Query
163    pub sel: QuerySel,
164    /// Session (S0-S3)
165    pub session: QuerySession,
166    /// Target (A or B)
167    pub target: QueryTarget,
168    /// Q value (0-15) - controls slot count
169    pub q: u8,
170}
171
172impl Default for QueryParams {
173    fn default() -> Self {
174        Self {
175            sel: QuerySel::All,
176            session: QuerySession::S0,
177            target: QueryTarget::A,
178            q: 4,
179        }
180    }
181}
182
183impl QueryParams {
184    /// Encode query parameters to 2-byte protocol format
185    pub fn to_bytes(&self) -> [u8; 2] {
186        // Format: DR(1) | M(2) | TRext(1) | Sel(2) | Session(2) | Target(1) | Q(4) | padding(3)
187        // DR = 0 (DR=8), M = 0 (M=1), TRext = 1 (use pilot tone)
188        let byte0 = 0x10 | ((self.sel as u8) << 2) | (self.session as u8);
189        let byte1 = ((self.target as u8) << 7) | ((self.q & 0x0F) << 3);
190        [byte0, byte1]
191    }
192
193    /// Decode query parameters from 2-byte protocol format
194    pub fn from_bytes(bytes: [u8; 2]) -> Self {
195        let sel = match (bytes[0] >> 2) & 0x03 {
196            0 | 1 => QuerySel::All,
197            2 => QuerySel::NotSl,
198            3 => QuerySel::Sl,
199            _ => QuerySel::All,
200        };
201        let session = match bytes[0] & 0x03 {
202            0 => QuerySession::S0,
203            1 => QuerySession::S1,
204            2 => QuerySession::S2,
205            3 => QuerySession::S3,
206            _ => QuerySession::S0,
207        };
208        let target = if (bytes[1] >> 7) & 0x01 == 0 {
209            QueryTarget::A
210        } else {
211            QueryTarget::B
212        };
213        let q = (bytes[1] >> 3) & 0x0F;
214
215        Self {
216            sel,
217            session,
218            target,
219            q,
220        }
221    }
222}
223
224/// Sel field for Query command
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
226#[repr(u8)]
227pub enum QuerySel {
228    /// All tags respond
229    #[default]
230    All = 0x00,
231    /// Only tags with SL deasserted respond
232    NotSl = 0x02,
233    /// Only tags with SL asserted respond
234    Sl = 0x03,
235}
236
237/// Session for Query command
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
239#[repr(u8)]
240pub enum QuerySession {
241    #[default]
242    S0 = 0x00,
243    S1 = 0x01,
244    S2 = 0x02,
245    S3 = 0x03,
246}
247
248/// Target flag for Query command
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
250#[repr(u8)]
251pub enum QueryTarget {
252    #[default]
253    A = 0x00,
254    B = 0x01,
255}
256
257impl PartialEq for TagInfo {
258    fn eq(&self, other: &Self) -> bool {
259        self.epc == other.epc
260    }
261}
262
263/// Lock action for tag memory operations
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265#[repr(u8)]
266pub enum LockAction {
267    /// Unlock (read/write accessible without password)
268    Unlock = 0x00,
269    /// Lock (password required for read/write)
270    Lock = 0x01,
271    /// Permanent unlock (cannot be locked)
272    PermUnlock = 0x02,
273    /// Permanent lock (password always required, cannot be unlocked)
274    PermLock = 0x03,
275}
276
277/// Lock target for specifying which memory area to lock
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
279#[repr(u8)]
280pub enum LockTarget {
281    /// User memory bank
282    User = 0x01,
283    /// TID memory bank
284    Tid = 0x02,
285    /// EPC memory bank
286    Epc = 0x03,
287    /// Access password
288    AccessPassword = 0x04,
289    /// Kill password
290    KillPassword = 0x05,
291}
292
293/// Lock mask and action payload
294#[derive(Debug, Clone)]
295pub struct LockPayload {
296    /// Which memory area to apply the lock action to
297    pub target: LockTarget,
298    /// The lock action to perform
299    pub action: LockAction,
300}
301
302impl LockPayload {
303    /// Encode lock payload to 3-byte protocol format (mask and action)
304    pub fn to_bytes(&self) -> [u8; 3] {
305        // The lock payload format per EPC Gen2:
306        // Mask (10 bits) + Action (10 bits) = 20 bits = 2.5 bytes, padded to 3 bytes
307        //
308        // Bits map to memory areas (2 bits per area):
309        // Kill pwd | Access pwd | EPC | TID | User
310        //
311        // Mask bits: 1 = apply action, 0 = don't change
312        // Action bits per area: bit1=permalock, bit0=lock
313        //   00 = unlock, 01 = lock, 10 = perm unlock, 11 = perm lock
314
315        let shift = match self.target {
316            LockTarget::User => 0,
317            LockTarget::Tid => 2,
318            LockTarget::Epc => 4,
319            LockTarget::AccessPassword => 6,
320            LockTarget::KillPassword => 8,
321        };
322
323        // Set mask bits for target (2 bits)
324        let mask: u16 = 0x03 << shift;
325
326        // Set action bits for target
327        let action: u16 = (self.action as u16) << shift;
328
329        // Combine into 20-bit payload (mask in high 10 bits, action in low 10 bits)
330        // Format: [mask_hi, mask_lo|action_hi, action_lo]
331        let payload: u32 = ((mask as u32) << 10) | (action as u32);
332
333        [
334            ((payload >> 16) & 0xFF) as u8,
335            ((payload >> 8) & 0xFF) as u8,
336            (payload & 0xFF) as u8,
337        ]
338    }
339}
340
341/// RF link profile for modulation settings
342#[derive(Debug, Clone, Copy, PartialEq, Eq)]
343#[repr(u8)]
344pub enum RfLinkProfile {
345    /// FM0 40KHz link frequency
346    Fm0_40kHz = 0xD0,
347    /// FM0 400KHz link frequency
348    Fm0_400kHz = 0xD1,
349    /// Miller 4, 250KHz link frequency
350    Miller4_250kHz = 0xD2,
351    /// Miller 4, 300KHz link frequency
352    Miller4_300kHz = 0xD3,
353    /// Miller 2, 40KHz link frequency (Dense Reader Mode)
354    Miller2_40kHzDrm = 0xD4,
355}
356
357impl TryFrom<u8> for RfLinkProfile {
358    type Error = ();
359
360    fn try_from(value: u8) -> Result<Self, Self::Error> {
361        match value {
362            0xD0 => Ok(RfLinkProfile::Fm0_40kHz),
363            0xD1 => Ok(RfLinkProfile::Fm0_400kHz),
364            0xD2 => Ok(RfLinkProfile::Miller4_250kHz),
365            0xD3 => Ok(RfLinkProfile::Miller4_300kHz),
366            0xD4 => Ok(RfLinkProfile::Miller2_40kHzDrm),
367            _ => Err(()),
368        }
369    }
370}
371
372/// QT control settings for Impinj Monza tags
373#[derive(Debug, Clone, Copy, PartialEq, Eq)]
374pub struct QtControl {
375    /// Short Range mode: reduces backscatter strength
376    pub short_range: bool,
377    /// QT persistence: false = temporary, true = permanent
378    pub persistence: bool,
379}
380
381impl QtControl {
382    /// Encode to protocol byte
383    pub fn to_byte(&self) -> u8 {
384        let mut byte = 0u8;
385        if self.short_range {
386            byte |= 0x01;
387        }
388        if self.persistence {
389            byte |= 0x02;
390        }
391        byte
392    }
393}
394
395/// Errors that can occur during RFID operations
396#[derive(Debug)]
397pub enum UhfError {
398    /// Transport layer error (UART, serial, etc.)
399    Transport(String),
400    /// Invalid parameter passed to a function
401    InvalidParameter(String),
402    /// Invalid response received from the reader
403    InvalidResponse(String),
404}
405
406/// Convert bytes to uppercase hex string
407pub(crate) fn bytes_to_hex(bytes: &[u8]) -> String {
408    bytes.iter().map(|b| format!("{:02X}", b)).collect()
409}