rfid_silion_compat/command.rs
1//! Command-layer protocol types and host packet builders.
2//!
3//! This module contains low-level request payload models and helpers for
4//! constructing wire packets for Silion reader commands.
5
6use crate::async_proto::{ASYNC_MARKER, ASYNC_TERMINATOR, subcommand_crc};
7use crate::codes::{AntennaPortsOption, CommandCode, RegionCode};
8use crate::error::ProtocolError;
9use crate::frame::{build_host_frame, push_u16_be, push_u32_be};
10use crate::parsers::{AntennaPair, AntennaPower, AntennaPowerSettling};
11
12/// Select/singulation payload used by inventory and tag access commands.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct SelectContent {
15 /// Address (bits).
16 pub address_bits: u32,
17 /// Number of selected bits.
18 pub bit_len: u16,
19 /// Select data bytes.
20 pub data: Vec<u8>,
21}
22
23impl SelectContent {
24 /// Encode the SelectContent according to the Silion protocol, using select_option_bits to determine encoding.
25 ///
26 /// - If select_option_bits.extended_data_length() is true, bit_len is encoded as u16 (big-endian), otherwise as u8.
27 /// - Data is encoded as-is.
28 /// Encode the SelectContent according to the Silion protocol, using InventoryOption to determine encoding.
29 ///
30 /// - If option.select_option_bits_struct().extended_data_length() is true, bit_len is encoded as u16 (big-endian), otherwise as u8.
31 /// - Data is encoded as-is.
32 pub(crate) fn encode_with_option(&self, out: &mut Vec<u8>, option: InventoryOption) {
33 let select_option_bits = option.select_option_bits();
34 if select_option_bits.mode() != Some(SelectMode::Epc) {
35 push_u32_be(out, self.address_bits);
36 }
37 if select_option_bits.extended_data_length() {
38 // Encode bit_len as u16 (big-endian)
39 out.push((self.bit_len >> 8) as u8);
40 out.push((self.bit_len & 0xFF) as u8);
41 } else {
42 // Encode bit_len as u8
43 out.push(self.bit_len as u8);
44 }
45 out.extend_from_slice(&self.data);
46 }
47}
48
49/// Tag singulation/select operation mode for inventory commands.
50///
51/// These values represent the target memory bank or select operation mode as defined
52/// in the Silion protocol for Tag Inventory commands. They occupy bits 0-3 of the
53/// select-option field.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55#[repr(u8)]
56pub enum SelectMode {
57 /// Select functionality is disabled. First tag found will be the tag operated on.
58 /// No other Tag Singulation Fields should be specified.
59 /// Note: When Select is disabled, commands do not support an access password.
60 /// Use `SelectMode::PasswordOnly` to send a password without Select Content.
61 Disabled = 0x00,
62 /// Select on the value of the EPC.
63 Epc = 0x01,
64 /// Select on contents of TID memory bank (Gen2 bank 0x02).
65 Tid = 0x02,
66 /// Select on contents of User Memory bank (Gen2 bank 0x03).
67 UserMemory = 0x03,
68 /// Select on contents of the EPC memory bank (Gen2 bank 0x01).
69 EpcBank = 0x04,
70 /// Use this option to specify an access password without performing a Select.
71 /// When this option is used, do not pass a Select Content field.
72 PasswordOnly = 0x05,
73}
74
75impl SelectMode {
76 /// Return the raw protocol value.
77 pub const fn as_u8(self) -> u8 {
78 self as u8
79 }
80
81 /// Create from the raw protocol value.
82 pub const fn from_u8(value: u8) -> Option<Self> {
83 match value {
84 0x00 => Some(SelectMode::Disabled),
85 0x01 => Some(SelectMode::Epc),
86 0x02 => Some(SelectMode::Tid),
87 0x03 => Some(SelectMode::UserMemory),
88 0x04 => Some(SelectMode::EpcBank),
89 0x05 => Some(SelectMode::PasswordOnly),
90 _ => None,
91 }
92 }
93}
94
95/// Select-option bits for inventory commands.
96///
97/// The select-option field (bits 0, 1, 2, 3, 5 of the option byte) controls tag
98/// singulation/select behavior. Includes the select mode plus optional flags.
99///
100/// | Mode | Value | Meaning |
101/// |------|-------|---------|
102/// | Select Mode (bits 0-2) | 0x00-0x05 | Target memory bank or operation (see [`SelectMode`]) |
103/// | Invert Flag (bit 3) | 0x08 | Invert matching: return tags that do NOT match |
104/// | Extended Data Length (bit 5) | 0x20 | Select Data Length is 2 bytes instead of 1 |
105///
106/// # Examples
107///
108/// Create a select option for EPC matching:
109/// ```
110/// use rfid_silion_compat::command::{SelectMode, SelectOptionBits};
111///
112/// let opts = SelectOptionBits::new(SelectMode::Epc);
113/// assert_eq!(opts.raw(), 0x01);
114/// ```
115///
116/// Create a select option with invert flag:
117/// ```
118/// use rfid_silion_compat::command::{SelectMode, SelectOptionBits};
119///
120/// let opts = SelectOptionBits::new(SelectMode::UserMemory)
121/// .with_invert_flag(true);
122/// assert_eq!(opts.raw(), 0x0B); // 0x03 | 0x08
123/// ```
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub struct SelectOptionBits(u8);
126
127impl SelectOptionBits {
128 /// Create with the given select mode and no additional flags.
129 pub const fn new(mode: SelectMode) -> Self {
130 Self(mode.as_u8())
131 }
132
133 /// Create from the raw protocol byte (lower bits only, 0x2F mask).
134 pub const fn from_raw(raw: u8) -> Self {
135 Self(raw & 0x2F)
136 }
137
138 /// Return the raw protocol byte (includes all select-option bits).
139 pub const fn raw(self) -> u8 {
140 self.0
141 }
142
143 /// Return the select mode (bits 0-2, values 0x00-0x05).
144 pub const fn mode(self) -> Option<SelectMode> {
145 SelectMode::from_u8(self.0 & 0x0F)
146 }
147
148 /// Return whether the invert flag is set (bit 3).
149 ///
150 /// When set, tags NOT matching the specified Tag Singulation Fields will be returned.
151 pub const fn invert_flag(self) -> bool {
152 (self.0 & 0x08) != 0
153 }
154
155 /// Return whether the extended data length flag is set (bit 5).
156 ///
157 /// When set, Select Data Length is 2 bytes instead of 1, allowing Select Data
158 /// to be greater than 255 bits.
159 pub const fn extended_data_length(self) -> bool {
160 (self.0 & 0x20) != 0
161 }
162
163 /// Set or clear the invert flag (bit 3).
164 pub const fn with_invert_flag(self, en: bool) -> Self {
165 Self(if en { self.0 | 0x08 } else { self.0 & !0x08 })
166 }
167
168 /// Set or clear the extended data length flag (bit 5).
169 pub const fn with_extended_data_length(self, en: bool) -> Self {
170 Self(if en { self.0 | 0x20 } else { self.0 & !0x20 })
171 }
172}
173
174impl From<SelectMode> for SelectOptionBits {
175 fn from(mode: SelectMode) -> Self {
176 Self::new(mode)
177 }
178}
179
180impl From<u8> for SelectOptionBits {
181 fn from(value: u8) -> Self {
182 Self::from_raw(value)
183 }
184}
185
186impl From<SelectOptionBits> for u8 {
187 fn from(value: SelectOptionBits) -> Self {
188 value.raw()
189 }
190}
191
192/// Option byte used by inventory commands (for example `0x22` and async start `0xAA48`).
193///
194/// Lower bits include select-option flags documented under Tag Inventory
195/// commands. Higher bits are command-specific non-select option flags.
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub struct InventoryOption(u8);
198
199impl InventoryOption {
200 /// Create a default option byte with all bits cleared.
201 pub const fn default() -> Self {
202 Self(0)
203 }
204
205 /// Create from the raw protocol byte.
206 pub const fn from_raw(raw: u8) -> Self {
207 Self(raw)
208 }
209
210 /// Return the raw protocol byte.
211 pub const fn raw(self) -> u8 {
212 self.0
213 }
214
215 /// Return select-option bits as a SelectOptionBits struct.
216 pub const fn select_option_bits(self) -> SelectOptionBits {
217 SelectOptionBits::from_raw(self.0 & 0x2F)
218 }
219
220 /// Return whether Single Tag Inventory metadata mode is enabled (bit 4 / `0x10`).
221 ///
222 /// For command `0x21`: when enabled, host command includes 2-byte Metadata Flags
223 /// and reader returns EPC + metadata. When disabled, command omits Metadata Flags
224 /// and reader returns EPC-only payload.
225 pub const fn single_tag_metadata_enabled(self) -> bool {
226 (self.0 & 0x10) != 0
227 }
228
229 /// Set or clear Single Tag Inventory metadata mode bit (bit 4 / `0x10`).
230 pub const fn with_single_tag_metadata(self, enabled: bool) -> Self {
231 if enabled {
232 Self(self.0 | 0x10)
233 } else {
234 Self(self.0 & !0x10)
235 }
236 }
237}
238
239impl From<u8> for InventoryOption {
240 fn from(value: u8) -> Self {
241 Self::from_raw(value)
242 }
243}
244
245impl From<SelectOptionBits> for InventoryOption {
246 fn from(bits: SelectOptionBits) -> Self {
247 Self(bits.raw())
248 }
249}
250
251impl From<InventoryOption> for u8 {
252 fn from(value: InventoryOption) -> Self {
253 value.raw()
254 }
255}
256
257/// Search flags used by inventory commands (for example `0x22` and `0xAA48`).
258///
259/// For asynchronous inventory, protocol docs define extra semantics:
260/// - bits 8..=11: rest ratio steps (0..=15)
261/// - bit 15: heartbeat enable
262/// - bit 14: auto-stop enable
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264#[cfg_attr(feature = "web-serial", derive(serde::Serialize))]
265pub struct InventorySearchFlags(u16);
266
267impl InventorySearchFlags {
268 /// Create a default search flags value with all bits cleared.
269 pub const fn new() -> Self {
270 Self(0)
271 }
272
273 /// Create from the raw protocol value.
274 pub const fn from_raw(raw: u16) -> Self {
275 Self(raw)
276 }
277
278 /// Return the raw protocol value.
279 pub const fn raw(self) -> u16 {
280 self.0
281 }
282
283 /// Extract asynchronous rest-ratio steps from bits 8..=11.
284 pub const fn async_rest_ratio_steps(self) -> u8 {
285 ((self.0 >> 8) & 0x0F) as u8
286 }
287
288 /// Return whether asynchronous heartbeat is enabled (bit 15).
289 pub const fn async_heartbeat_enabled(self) -> bool {
290 (self.0 & 0x8000) != 0
291 }
292
293 /// Return whether asynchronous auto-stop is enabled (bit 14).
294 pub const fn async_auto_stop_enabled(self) -> bool {
295 (self.0 & 0x4000) != 0
296 }
297
298 /// Return whether inventory embedded command mode is enabled (bit 2).
299 ///
300 /// This bit is documented for command `0x22` and reused by
301 /// asynchronous inventory start (`0xAA48`).
302 pub const fn embedded_command_enabled(self) -> bool {
303 (self.0 & 0x0004) != 0
304 }
305
306 /// Set asynchronous rest-ratio steps (0..=15) in bits 8..=11.
307 pub fn with_async_rest_ratio_steps(self, steps: u8) -> Result<Self, ProtocolError> {
308 if steps > 15 {
309 return Err(ProtocolError::InvalidArgument(
310 "async rest ratio steps must be in 0..=15",
311 ));
312 }
313 let raw = (self.0 & !(0x0F << 8)) | ((steps as u16) << 8);
314 Ok(Self(raw))
315 }
316
317 /// Set or clear asynchronous heartbeat enable (bit 15).
318 pub const fn with_async_heartbeat(self, enabled: bool) -> Self {
319 if enabled {
320 Self(self.0 | 0x8000)
321 } else {
322 Self(self.0 & !0x8000)
323 }
324 }
325
326 /// Set or clear asynchronous auto-stop enable (bit 14).
327 pub const fn with_async_auto_stop(self, enabled: bool) -> Self {
328 if enabled {
329 Self(self.0 | 0x4000)
330 } else {
331 Self(self.0 & !0x4000)
332 }
333 }
334
335 /// Set or clear inventory embedded command mode (bit 2).
336 pub const fn with_embedded_command(self, enabled: bool) -> Self {
337 if enabled {
338 Self(self.0 | 0x0004)
339 } else {
340 Self(self.0 & !0x0004)
341 }
342 }
343}
344
345impl From<u16> for InventorySearchFlags {
346 fn from(value: u16) -> Self {
347 Self::from_raw(value)
348 }
349}
350
351impl From<InventorySearchFlags> for u16 {
352 fn from(value: InventorySearchFlags) -> Self {
353 value.raw()
354 }
355}
356
357impl Default for InventorySearchFlags {
358 fn default() -> Self {
359 Self::new()
360 }
361}
362
363/// Metadata flags that control which per-tag metadata fields the reader
364/// includes in inventory responses.
365///
366/// Defined in the Single Tag Inventory (`0x21`), Get Tag Buffer (`0x29`), and
367/// Asynchronous Inventory (`0xAA`) command specifications.
368///
369/// Each enabled bit requests one additional metadata field in the response.
370/// When all bits are zero the reader returns only the EPC and tag CRC.
371///
372/// | Bit | Value | Field | Size | Description |
373/// |-----|--------|---------------|---------|-------------|
374/// | 0 | 0x0001 | Read Count | 1 byte | Number of times the tag was archived |
375/// | 1 | 0x0002 | RSSI | 1 byte | Signal strength, signed (dBm) |
376/// | 2 | 0x0004 | Antenna ID | 1 byte | Logic antenna number |
377/// | 3 | 0x0008 | Frequency | 3 bytes | Frequency at archival (kHz) |
378/// | 4 | 0x0010 | Timestamp | 4 bytes | Elapsed time from inventory start (ms) |
379/// | 5 | 0x0020 | RFU | 2 bytes | Reserved for future use |
380/// | 6 | 0x0040 | Protocol ID | 1 byte | Tag protocol (0x05 = Gen2) |
381/// | 7 | 0x0080 | Data Length | 2 bytes | Tag data length (0x0000 for 0x21) |
382#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
383#[cfg_attr(feature = "web-serial", derive(serde::Serialize))]
384pub struct MetadataFlags(u16);
385
386impl MetadataFlags {
387 /// No metadata: only EPC and tag CRC are returned.
388 pub const NONE: Self = Self(0x0000);
389 /// All defined metadata bits set (`0x00FF`).
390 pub const ALL: Self = Self(0x00FF);
391
392 /// Create from the raw protocol value.
393 pub const fn from_raw(raw: u16) -> Self {
394 Self(raw)
395 }
396
397 /// Return the raw protocol value.
398 pub const fn raw(self) -> u16 {
399 self.0
400 }
401
402 /// Whether the Read Count field is requested (bit 0).
403 pub const fn read_count(self) -> bool {
404 (self.0 & 0x0001) != 0
405 }
406 /// Whether the RSSI field is requested (bit 1).
407 pub const fn rssi(self) -> bool {
408 (self.0 & 0x0002) != 0
409 }
410 /// Whether the Antenna ID field is requested (bit 2).
411 pub const fn antenna_id(self) -> bool {
412 (self.0 & 0x0004) != 0
413 }
414 /// Whether the Frequency field is requested (bit 3).
415 pub const fn frequency(self) -> bool {
416 (self.0 & 0x0008) != 0
417 }
418 /// Whether the Timestamp field is requested (bit 4).
419 pub const fn timestamp(self) -> bool {
420 (self.0 & 0x0010) != 0
421 }
422 /// Whether the RFU reserved field is requested (bit 5).
423 pub const fn rfu(self) -> bool {
424 (self.0 & 0x0020) != 0
425 }
426 /// Whether the Protocol ID field is requested (bit 6).
427 pub const fn protocol_id(self) -> bool {
428 (self.0 & 0x0040) != 0
429 }
430 /// Whether the Data Length field is requested (bit 7).
431 pub const fn data_length(self) -> bool {
432 (self.0 & 0x0080) != 0
433 }
434
435 /// Set or clear the Read Count bit (bit 0).
436 pub const fn with_read_count(self, en: bool) -> Self {
437 Self(if en {
438 self.0 | 0x0001
439 } else {
440 self.0 & !0x0001
441 })
442 }
443 /// Set or clear the RSSI bit (bit 1).
444 pub const fn with_rssi(self, en: bool) -> Self {
445 Self(if en {
446 self.0 | 0x0002
447 } else {
448 self.0 & !0x0002
449 })
450 }
451 /// Set or clear the Antenna ID bit (bit 2).
452 pub const fn with_antenna_id(self, en: bool) -> Self {
453 Self(if en {
454 self.0 | 0x0004
455 } else {
456 self.0 & !0x0004
457 })
458 }
459 /// Set or clear the Frequency bit (bit 3).
460 pub const fn with_frequency(self, en: bool) -> Self {
461 Self(if en {
462 self.0 | 0x0008
463 } else {
464 self.0 & !0x0008
465 })
466 }
467 /// Set or clear the Timestamp bit (bit 4).
468 pub const fn with_timestamp(self, en: bool) -> Self {
469 Self(if en {
470 self.0 | 0x0010
471 } else {
472 self.0 & !0x0010
473 })
474 }
475 /// Set or clear the RFU reserved bit (bit 5).
476 pub const fn with_rfu(self, en: bool) -> Self {
477 Self(if en {
478 self.0 | 0x0020
479 } else {
480 self.0 & !0x0020
481 })
482 }
483 /// Set or clear the Protocol ID bit (bit 6).
484 pub const fn with_protocol_id(self, en: bool) -> Self {
485 Self(if en {
486 self.0 | 0x0040
487 } else {
488 self.0 & !0x0040
489 })
490 }
491 /// Set or clear the Data Length bit (bit 7).
492 pub const fn with_data_length(self, en: bool) -> Self {
493 Self(if en {
494 self.0 | 0x0080
495 } else {
496 self.0 & !0x0080
497 })
498 }
499}
500
501impl From<u16> for MetadataFlags {
502 fn from(value: u16) -> Self {
503 Self::from_raw(value)
504 }
505}
506
507impl From<MetadataFlags> for u16 {
508 fn from(value: MetadataFlags) -> Self {
509 value.raw()
510 }
511}
512
513/// Tag memory bank selector used by read/write access commands.
514#[derive(Debug, Clone, Copy, PartialEq, Eq)]
515#[repr(u8)]
516pub enum MemBank {
517 /// Gen2 Reserved bank (`0x00`).
518 Reserved = 0x00,
519 /// Gen2 EPC bank (`0x01`).
520 Epc = 0x01,
521 /// Gen2 TID bank (`0x02`).
522 Tid = 0x02,
523 /// Gen2 User bank (`0x03`).
524 User = 0x03,
525}
526
527impl MemBank {
528 /// Return the raw protocol value.
529 pub const fn as_u8(self) -> u8 {
530 self as u8
531 }
532
533 /// Parse a raw protocol value into a typed memory bank.
534 pub fn from_u8(raw: u8) -> Result<Self, ProtocolError> {
535 match raw {
536 0x00 => Ok(Self::Reserved),
537 0x01 => Ok(Self::Epc),
538 0x02 => Ok(Self::Tid),
539 0x03 => Ok(Self::User),
540 _ => Err(ProtocolError::InvalidArgument(
541 "membank must be one of 0x00..=0x03",
542 )),
543 }
544 }
545}
546
547impl From<MemBank> for u8 {
548 fn from(value: MemBank) -> Self {
549 value.as_u8()
550 }
551}
552
553/// Typed inventory embedded command content.
554///
555/// The protocol currently documents embedded command opcode `0x28`
556/// (Read Tag Data) for inventory commands.
557#[derive(Debug, Clone, PartialEq, Eq)]
558pub enum InventoryEmbeddedCommandContent {
559 /// Embedded command `0x28` (Read Tag Data).
560 ReadTagData(EmbeddedReadTagData),
561}
562
563impl InventoryEmbeddedCommandContent {
564 fn encoded_len(&self) -> usize {
565 match self {
566 Self::ReadTagData(cmd) => 3 + cmd.data_field_len(),
567 }
568 }
569
570 fn encode(&self, out: &mut Vec<u8>) {
571 match self {
572 Self::ReadTagData(cmd) => cmd.encode(out),
573 }
574 }
575}
576
577/// Typed fields for embedded command `0x28` (Read Tag Data).
578#[derive(Debug, Clone, PartialEq, Eq)]
579pub struct EmbeddedReadTagData {
580 /// Target memory bank.
581 pub read_membank: MemBank,
582 /// Start address in words.
583 pub read_address_words: u32,
584 /// Number of words to read.
585 pub word_count: u8,
586}
587
588impl EmbeddedReadTagData {
589 fn data_field_len(&self) -> usize {
590 // Timeout(2) + Option(1) + MemBank(1) + Address(4) + WordCount(1)
591 9
592 }
593
594 fn encode(&self, out: &mut Vec<u8>) {
595 // Embedded command frame format:
596 // Count(1)=1 | Length(1) | Opcode(1=0x28) | DataField(9)
597 out.push(0x01);
598 out.push(self.data_field_len() as u8);
599 out.push(CommandCode::ReadTagData.as_u8());
600
601 // Vendor docs state timeout/option are ignored for embedded reads.
602 push_u16_be(out, 0x0000);
603 out.push(0x00);
604 out.push(self.read_membank.as_u8());
605 push_u32_be(out, self.read_address_words);
606 out.push(self.word_count);
607 }
608}
609
610/// Asynchronous inventory subcommand IDs.
611#[derive(Debug, Clone, Copy, PartialEq, Eq)]
612#[repr(u16)]
613pub enum AsyncSubcommandCode {
614 /// Start async inventory.
615 Start = 0xAA48,
616 /// Stop async inventory.
617 Stop = 0xAA49,
618}
619
620/// Typed subcommand data for Start AsyncInventory (`0xAA48`).
621#[derive(Debug, Clone, PartialEq, Eq)]
622pub struct AsyncInventoryStartData {
623 /// Metadata flags controlling which per-tag fields the reader returns.
624 pub metadata_flags: MetadataFlags,
625 /// Option byte, same meaning as command `0x22` select option bits.
626 pub option: InventoryOption,
627 /// Search flags (2 bytes), same meaning as command `0x22`.
628 pub search_flags: InventorySearchFlags,
629 /// Optional access password (4 bytes) when required by option.
630 pub access_password: Option<u32>,
631 /// Optional select content when select operation is enabled.
632 pub select_content: Option<SelectContent>,
633 /// Optional typed embedded command content.
634 pub embedded_command_content: Option<InventoryEmbeddedCommandContent>,
635}
636
637impl AsyncInventoryStartData {
638 fn encode(&self) -> Vec<u8> {
639 let mut data = Vec::with_capacity(
640 2 + 1
641 + 2
642 + if self.access_password.is_some() { 4 } else { 0 }
643 + self
644 .select_content
645 .as_ref()
646 .map(|s| 4 + 2 + s.data.len()) // worst case: 2 bytes for bit_len
647 .unwrap_or(0)
648 + self
649 .embedded_command_content
650 .as_ref()
651 .map(InventoryEmbeddedCommandContent::encoded_len)
652 .unwrap_or(0),
653 );
654
655 push_u16_be(&mut data, self.metadata_flags.raw());
656 data.push(self.option.raw());
657 push_u16_be(&mut data, self.search_flags.raw());
658
659 if let Some(password) = self.access_password {
660 push_u32_be(&mut data, password);
661 }
662
663 if let Some(select) = &self.select_content {
664 select.encode_with_option(&mut data, self.option);
665 }
666
667 if let Some(embedded) = &self.embedded_command_content {
668 embedded.encode(&mut data);
669 }
670 data
671 }
672}
673
674/// Typed payload variants for command `0x91` (Set Antenna Ports).
675#[derive(Debug, Clone, PartialEq, Eq)]
676pub enum AntennaPortsConfiguration {
677 /// Set the single TX/RX pair used for tag access operations.
678 AccessPair(AntennaPair),
679 /// Set the ordered TX/RX pairs used during inventory operations.
680 InventoryPairs(Vec<AntennaPair>),
681 /// Set read/write power per logical TX antenna.
682 ///
683 /// Power fields use `0.01 dBm` units in the protocol docs, though current
684 /// firmware is documented as effectively applying about `1 dBm` precision.
685 Power(Vec<AntennaPower>),
686 /// Set read/write power and settling time per logical TX antenna.
687 ///
688 /// Power fields use `0.01 dBm` units in the protocol docs, though current
689 /// firmware is documented as effectively applying about `1 dBm` precision.
690 PowerAndSettling(Vec<AntennaPowerSettling>),
691}
692
693impl AntennaPortsConfiguration {
694 fn encode(&self) -> Result<Vec<u8>, ProtocolError> {
695 match self {
696 Self::AccessPair(pair) => Ok(vec![
697 AntennaPortsOption::AccessPair.as_u8(),
698 pair.tx,
699 pair.rx,
700 ]),
701 Self::InventoryPairs(pairs) => {
702 if pairs.is_empty() {
703 return Err(ProtocolError::InvalidArgument(
704 "inventory antenna pairs cannot be empty",
705 ));
706 }
707 let mut data = Vec::with_capacity(1 + pairs.len() * 2);
708 data.push(AntennaPortsOption::InventoryPairs.as_u8());
709 for pair in pairs {
710 data.push(pair.tx);
711 data.push(pair.rx);
712 }
713 Ok(data)
714 }
715 Self::Power(entries) => {
716 if entries.is_empty() {
717 return Err(ProtocolError::InvalidArgument(
718 "antenna power entries cannot be empty",
719 ));
720 }
721 let mut data = Vec::with_capacity(1 + entries.len() * 5);
722 data.push(AntennaPortsOption::Power.as_u8());
723 for entry in entries {
724 data.push(entry.tx);
725 push_u16_be(&mut data, entry.read_power);
726 push_u16_be(&mut data, entry.write_power);
727 }
728 Ok(data)
729 }
730 Self::PowerAndSettling(entries) => {
731 if entries.is_empty() {
732 return Err(ProtocolError::InvalidArgument(
733 "antenna power and settling entries cannot be empty",
734 ));
735 }
736 let mut data = Vec::with_capacity(1 + entries.len() * 7);
737 data.push(AntennaPortsOption::PowerAndSettling.as_u8());
738 for entry in entries {
739 data.push(entry.tx);
740 push_u16_be(&mut data, entry.read_power);
741 push_u16_be(&mut data, entry.write_power);
742 push_u16_be(&mut data, entry.settling_time_us);
743 }
744 Ok(data)
745 }
746 }
747 }
748}
749
750/// Command builders for all protocol command groups.
751pub struct HostCommand;
752
753impl HostCommand {
754 /// Build a raw command packet from command code and data field bytes.
755 ///
756 /// Use this when the crate does not yet provide a typed builder for a
757 /// command variant you need.
758 ///
759 /// # Examples
760 /// ```rust
761 /// use rfid_silion_compat::command::HostCommand;
762 ///
763 /// let packet = HostCommand::raw(0x03, &[]).unwrap();
764 /// assert_eq!(packet, vec![0xFF, 0x00, 0x03, 0x1D, 0x0C]);
765 /// ```
766 pub fn raw(command: u8, data: &[u8]) -> Result<Vec<u8>, ProtocolError> {
767 build_host_frame(command, data)
768 }
769
770 /// Build command `0x01` (Write Flash).
771 ///
772 /// This bootloader command writes firmware words into flash memory.
773 /// `finflag` indicates whether this is the final chunk (`0xFF` means last).
774 pub fn write_flash(
775 finflag: u8,
776 write_addr: u32,
777 write_data: &[u8],
778 ) -> Result<Vec<u8>, ProtocolError> {
779 if write_data.is_empty() || (write_data.len() % 4 != 0) {
780 return Err(ProtocolError::InvalidArgument(
781 "write_data must be non-empty and a multiple of 4 bytes",
782 ));
783 }
784 if write_data.len() > 128 {
785 return Err(ProtocolError::InvalidArgument(
786 "write_data cannot exceed 128 bytes",
787 ));
788 }
789 let words = (write_data.len() / 4) as u8;
790 let mut data = Vec::with_capacity(1 + 4 + 1 + write_data.len());
791 data.push(finflag);
792 push_u32_be(&mut data, write_addr);
793 data.push(words);
794 data.extend_from_slice(write_data);
795 build_host_frame(CommandCode::WriteFlash.as_u8(), &data)
796 }
797
798 /// Build command `0x02` (Read Flash).
799 ///
800 /// This bootloader command reads flash contents from `read_addr` for
801 /// `read_len_words * 4` bytes.
802 pub fn read_flash(read_addr: u32, read_len_words: u8) -> Result<Vec<u8>, ProtocolError> {
803 if read_len_words > 32 {
804 return Err(ProtocolError::InvalidArgument(
805 "read_len_words must be <= 32",
806 ));
807 }
808 let mut data = Vec::with_capacity(5);
809 push_u32_be(&mut data, read_addr);
810 data.push(read_len_words);
811 build_host_frame(CommandCode::ReadFlash.as_u8(), &data)
812 }
813
814 /// Build command `0x03` (Get Version).
815 ///
816 /// Reader replies with bootloader/hardware/firmware version fields and
817 /// supported protocol flags.
818 ///
819 /// # Examples
820 /// ```rust
821 /// use rfid_silion_compat::command::HostCommand;
822 ///
823 /// let packet = HostCommand::get_version().unwrap();
824 /// assert_eq!(packet, vec![0xFF, 0x00, 0x03, 0x1D, 0x0C]);
825 /// ```
826 pub fn get_version() -> Result<Vec<u8>, ProtocolError> {
827 build_host_frame(CommandCode::GetVersion.as_u8(), &[])
828 }
829
830 /// Build command `0x04` (Boot Firmware).
831 ///
832 /// Switches execution to app firmware when currently in bootloader.
833 pub fn boot_firmware() -> Result<Vec<u8>, ProtocolError> {
834 build_host_frame(CommandCode::BootFirmware.as_u8(), &[])
835 }
836
837 /// Build command `0x06` (Set Baud Rate).
838 ///
839 /// `baud_rate` is encoded as a 32-bit big-endian integer as required by
840 /// the protocol documentation.
841 ///
842 /// # Examples
843 /// ```rust
844 /// use rfid_silion_compat::command::HostCommand;
845 ///
846 /// // 115200 decimal = 0x0001C200
847 /// let packet = HostCommand::set_baud_rate(115_200).unwrap();
848 /// assert_eq!(packet, vec![0xFF, 0x04, 0x06, 0x00, 0x01, 0xC2, 0x00, 0xA4, 0x60]);
849 /// ```
850 pub fn set_baud_rate(baud_rate: u32) -> Result<Vec<u8>, ProtocolError> {
851 let mut data = Vec::with_capacity(4);
852 push_u32_be(&mut data, baud_rate);
853 build_host_frame(CommandCode::SetBaudRate.as_u8(), &data)
854 }
855
856 /// Build command `0x08` (Verify Firmware).
857 ///
858 /// This is the bootloader verification command used after firmware burn.
859 pub fn verify_firmware(
860 check_addr: u32,
861 check_data_len_words: u32,
862 check_crc: u32,
863 ) -> Result<Vec<u8>, ProtocolError> {
864 let mut data = Vec::with_capacity(12);
865 push_u32_be(&mut data, check_addr);
866 push_u32_be(&mut data, check_data_len_words);
867 push_u32_be(&mut data, check_crc);
868 build_host_frame(CommandCode::VerifyFirmware.as_u8(), &data)
869 }
870
871 /// Build command `0x09` (Boot Bootloader).
872 ///
873 /// Requests transition from app firmware back to bootloader.
874 pub fn boot_bootloader() -> Result<Vec<u8>, ProtocolError> {
875 build_host_frame(CommandCode::BootBootloader.as_u8(), &[])
876 }
877
878 /// Build command `0x0C` (Get Run Phase).
879 ///
880 /// Reader returns whether it is currently in bootloader or app firmware.
881 ///
882 /// # Examples
883 /// ```rust
884 /// use rfid_silion_compat::command::HostCommand;
885 ///
886 /// let packet = HostCommand::get_run_phase().unwrap();
887 /// assert_eq!(packet, vec![0xFF, 0x00, 0x0C, 0x1D, 0x03]);
888 /// ```
889 pub fn get_run_phase() -> Result<Vec<u8>, ProtocolError> {
890 build_host_frame(CommandCode::GetRunPhase.as_u8(), &[])
891 }
892
893 /// Build command `0x10` (Get Serial Number).
894 ///
895 /// `option` and `data_flags` are reserved by the vendor docs.
896 pub fn get_serial_number(option: u8, data_flags: u8) -> Result<Vec<u8>, ProtocolError> {
897 build_host_frame(CommandCode::GetSerialNumber.as_u8(), &[option, data_flags])
898 }
899
900 /// Build command `0x21` (Single Tag Inventory).
901 ///
902 /// Inventories one tag within `timeout_ms`. Optional metadata and select
903 /// filter fields follow the protocol option bits.
904 pub fn single_tag_inventory(
905 timeout_ms: u16,
906 option: InventoryOption,
907 metadata_flags: Option<MetadataFlags>,
908 select: Option<SelectContent>,
909 ) -> Result<Vec<u8>, ProtocolError> {
910 if option.single_tag_metadata_enabled() && metadata_flags.is_none() {
911 return Err(ProtocolError::InvalidArgument(
912 "single_tag_inventory requires metadata_flags when option bit 4 (0x10) is set",
913 ));
914 }
915 if !option.single_tag_metadata_enabled() && metadata_flags.is_some() {
916 return Err(ProtocolError::InvalidArgument(
917 "single_tag_inventory must omit metadata_flags when option bit 4 (0x10) is clear",
918 ));
919 }
920
921 let mut data = Vec::new();
922 push_u16_be(&mut data, timeout_ms);
923 data.push(option.raw());
924 if let Some(flags) = metadata_flags {
925 push_u16_be(&mut data, flags.raw());
926 }
927 if let Some(sel) = select {
928 sel.encode_with_option(&mut data, option);
929 }
930 build_host_frame(CommandCode::SingleTagInventory.as_u8(), &data)
931 }
932
933 /// Build command `0x22` (Synchronous Inventory).
934 ///
935 /// Performs timed multi-tag inventory and archives tag results into reader
936 /// tag buffer for later retrieval by command `0x29`.
937 pub fn synchronous_inventory(
938 option: InventoryOption,
939 search_flags: u16,
940 timeout_ms: u16,
941 access_password: Option<u32>,
942 select: Option<SelectContent>,
943 embedded_command: Option<&[u8]>,
944 ) -> Result<Vec<u8>, ProtocolError> {
945 Self::synchronous_inventory_raw_embedded(
946 option,
947 InventorySearchFlags::from_raw(search_flags),
948 timeout_ms,
949 access_password,
950 select,
951 embedded_command,
952 )
953 }
954
955 /// Build command `0x22` (Synchronous Inventory) using typed option/flags
956 /// and typed embedded command content.
957 pub fn synchronous_inventory_typed(
958 option: InventoryOption,
959 search_flags: InventorySearchFlags,
960 timeout_ms: u16,
961 access_password: Option<u32>,
962 select: Option<SelectContent>,
963 embedded_command: Option<InventoryEmbeddedCommandContent>,
964 ) -> Result<Vec<u8>, ProtocolError> {
965 let mut data = Vec::new();
966 data.push(option.raw());
967 push_u16_be(&mut data, search_flags.raw());
968 push_u16_be(&mut data, timeout_ms);
969 if let Some(pw) = access_password {
970 push_u32_be(&mut data, pw);
971 }
972 if let Some(sel) = select {
973 sel.encode_with_option(&mut data, option);
974 }
975 if let Some(embedded) = embedded_command {
976 embedded.encode(&mut data);
977 }
978 build_host_frame(CommandCode::SynchronousInventory.as_u8(), &data)
979 }
980
981 /// Build command `0x22` (Synchronous Inventory) with raw embedded bytes.
982 ///
983 /// Prefer [`HostCommand::synchronous_inventory_typed`] where possible.
984 pub fn synchronous_inventory_raw_embedded(
985 option: InventoryOption,
986 search_flags: InventorySearchFlags,
987 timeout_ms: u16,
988 access_password: Option<u32>,
989 select: Option<SelectContent>,
990 embedded_command: Option<&[u8]>,
991 ) -> Result<Vec<u8>, ProtocolError> {
992 let mut data = Vec::new();
993 data.push(option.raw());
994 push_u16_be(&mut data, search_flags.raw());
995 push_u16_be(&mut data, timeout_ms);
996 if let Some(pw) = access_password {
997 push_u32_be(&mut data, pw);
998 }
999 if let Some(sel) = select {
1000 sel.encode_with_option(&mut data, option);
1001 }
1002 if let Some(embedded) = embedded_command {
1003 data.extend_from_slice(embedded);
1004 }
1005 build_host_frame(CommandCode::SynchronousInventory.as_u8(), &data)
1006 }
1007
1008 /// Build command `0x29` (Get Tag Buffer).
1009 ///
1010 /// Retrieves archived tag EPC/metadata records from synchronous inventory.
1011 pub fn get_tag_buffer(
1012 metadata_flags: MetadataFlags,
1013 option: InventoryOption,
1014 ) -> Result<Vec<u8>, ProtocolError> {
1015 let mut data = Vec::with_capacity(3);
1016 push_u16_be(&mut data, metadata_flags.raw());
1017 data.push(option.raw());
1018 build_host_frame(CommandCode::GetTagBuffer.as_u8(), &data)
1019 }
1020
1021 /// Build command `0x23` (Write Tag EPC).
1022 ///
1023 /// Writes EPC data and lets reader update EPC length bits in PC word.
1024 pub fn write_tag_epc(
1025 timeout_ms: u16,
1026 option: InventoryOption,
1027 access_password: Option<u32>,
1028 select: Option<SelectContent>,
1029 epc: &[u8],
1030 ) -> Result<Vec<u8>, ProtocolError> {
1031 if epc.is_empty() {
1032 return Err(ProtocolError::InvalidArgument("epc cannot be empty"));
1033 }
1034 let mut data = Vec::new();
1035 push_u16_be(&mut data, timeout_ms);
1036 data.push(option.raw());
1037 if option.raw() == 0x00 {
1038 data.push(0x00); // RFU byte required when option is 0x00
1039 }
1040 if option.raw() != 0x00 {
1041 if let Some(pw) = access_password {
1042 push_u32_be(&mut data, pw);
1043 } else {
1044 push_u32_be(&mut data, 0x00000000);
1045 }
1046 }
1047 if let Some(sel) = select {
1048 sel.encode_with_option(&mut data, option);
1049 }
1050 data.extend_from_slice(epc);
1051 build_host_frame(CommandCode::WriteTagEpc.as_u8(), &data)
1052 }
1053
1054 /// Build command `0x24` (Write Tag Data).
1055 ///
1056 /// Writes user-supplied bytes to a target bank/address on selected tag.
1057 pub fn write_tag_data(
1058 timeout_ms: u16,
1059 option: InventoryOption,
1060 write_address_words: u32,
1061 write_membank: MemBank,
1062 access_password: Option<u32>,
1063 select: Option<SelectContent>,
1064 write_data: &[u8],
1065 ) -> Result<Vec<u8>, ProtocolError> {
1066 if write_data.is_empty() || (write_data.len() % 2 != 0) {
1067 return Err(ProtocolError::InvalidArgument(
1068 "write_data must be non-empty and multiple of 2 bytes",
1069 ));
1070 }
1071 if write_data.len() > 64 {
1072 return Err(ProtocolError::InvalidArgument(
1073 "write_data must be <= 64 bytes",
1074 ));
1075 }
1076 let mut data = Vec::new();
1077 push_u16_be(&mut data, timeout_ms);
1078 data.push(option.raw());
1079 push_u32_be(&mut data, write_address_words);
1080 data.push(write_membank.as_u8());
1081 if let Some(pw) = access_password {
1082 push_u32_be(&mut data, pw);
1083 } else {
1084 push_u32_be(&mut data, 0x00000000);
1085 }
1086 if let Some(sel) = select {
1087 sel.encode_with_option(&mut data, option);
1088 }
1089 data.extend_from_slice(write_data);
1090 build_host_frame(CommandCode::WriteTagData.as_u8(), &data)
1091 }
1092
1093 /// Build command `0x25` (Lock Tag).
1094 ///
1095 /// Applies Gen2 lock actions defined by `mask_bits` and `action_bits`.
1096 pub fn lock_tag(
1097 timeout_ms: u16,
1098 option: InventoryOption,
1099 access_password: u32,
1100 mask_bits: u16,
1101 action_bits: u16,
1102 select: Option<SelectContent>,
1103 ) -> Result<Vec<u8>, ProtocolError> {
1104 let mut data = Vec::new();
1105 push_u16_be(&mut data, timeout_ms);
1106 data.push(option.raw());
1107 push_u32_be(&mut data, access_password);
1108 push_u16_be(&mut data, mask_bits);
1109 push_u16_be(&mut data, action_bits);
1110 if let Some(sel) = select {
1111 sel.encode_with_option(&mut data, option);
1112 }
1113 build_host_frame(CommandCode::LockTag.as_u8(), &data)
1114 }
1115
1116 /// Build command `0x26` (Kill Tag).
1117 ///
1118 /// Permanently kills a matching tag using `kill_password`.
1119 pub fn kill_tag(
1120 timeout_ms: u16,
1121 option: InventoryOption,
1122 kill_password: u32,
1123 select: Option<SelectContent>,
1124 ) -> Result<Vec<u8>, ProtocolError> {
1125 let mut data = Vec::new();
1126 push_u16_be(&mut data, timeout_ms);
1127 data.push(option.raw());
1128 push_u32_be(&mut data, kill_password);
1129 data.push(0x00); // RFU byte required by protocol docs
1130 if let Some(sel) = select {
1131 sel.encode_with_option(&mut data, option);
1132 }
1133 build_host_frame(CommandCode::KillTag.as_u8(), &data)
1134 }
1135
1136 /// Build command `0x28` (Read Tag Data).
1137 ///
1138 /// Reads memory words from a tag bank and optionally requests metadata.
1139 pub fn read_tag_data(
1140 timeout_ms: u16,
1141 option: InventoryOption,
1142 metadata_flags: Option<MetadataFlags>,
1143 read_membank: MemBank,
1144 read_address_words: u32,
1145 word_count: u8,
1146 access_password: Option<u32>,
1147 select: Option<SelectContent>,
1148 ) -> Result<Vec<u8>, ProtocolError> {
1149 if word_count == 0 || word_count > 96 {
1150 return Err(ProtocolError::InvalidArgument(
1151 "word_count must be in 1..=96",
1152 ));
1153 }
1154 let mut data = Vec::new();
1155 push_u16_be(&mut data, timeout_ms);
1156 data.push(option.raw());
1157 if let Some(flags) = metadata_flags {
1158 push_u16_be(&mut data, flags.raw());
1159 }
1160 data.push(read_membank.as_u8());
1161 push_u32_be(&mut data, read_address_words);
1162 data.push(word_count);
1163 if let Some(pw) = access_password {
1164 push_u32_be(&mut data, pw);
1165 } else {
1166 push_u32_be(&mut data, 0x00000000);
1167 }
1168 if let Some(sel) = select {
1169 sel.encode_with_option(&mut data, option);
1170 }
1171 println!("Read Tag Data command data: {:02X?}", data);
1172 build_host_frame(CommandCode::ReadTagData.as_u8(), &data)
1173 }
1174
1175 /// Build command `0x91` (Set Antenna Ports).
1176 ///
1177 /// The payload shape depends on the configuration variant and is encoded
1178 /// according to the option-specific layout from the protocol docs.
1179 ///
1180 /// # Examples
1181 /// ```rust
1182 /// use rfid_silion_compat::{AntennaPair, AntennaPortsConfiguration};
1183 /// use rfid_silion_compat::command::HostCommand;
1184 ///
1185 /// let packet = HostCommand::set_antenna_ports(&AntennaPortsConfiguration::AccessPair(
1186 /// AntennaPair { tx: 0x01, rx: 0x01 },
1187 /// ))
1188 /// .unwrap();
1189 /// assert_eq!(packet, vec![0xFF, 0x03, 0x91, 0x00, 0x01, 0x01, 0x62, 0x87]);
1190 /// ```
1191 pub fn set_antenna_ports(config: &AntennaPortsConfiguration) -> Result<Vec<u8>, ProtocolError> {
1192 let data = config.encode()?;
1193 build_host_frame(CommandCode::SetAntennaPorts.as_u8(), &data)
1194 }
1195
1196 /// Build command `0x93` (Set Current Tag Protocol).
1197 ///
1198 /// Current firmware expects `protocol` equal to `0x0005` (GEN2).
1199 pub fn set_current_tag_protocol(protocol: u16) -> Result<Vec<u8>, ProtocolError> {
1200 let mut data = Vec::with_capacity(2);
1201 push_u16_be(&mut data, protocol);
1202 build_host_frame(CommandCode::SetCurrentTagProtocol.as_u8(), &data)
1203 }
1204
1205 /// Build command `0x95` (Set Frequency Hopping).
1206 ///
1207 /// Sets hop table or reserved regulatory hopping time format.
1208 pub fn set_frequency_hopping(data_field: &[u8]) -> Result<Vec<u8>, ProtocolError> {
1209 build_host_frame(CommandCode::SetFrequencyHopping.as_u8(), data_field)
1210 }
1211
1212 /// Build command `0x96` (Set GPO / Get GPO status).
1213 ///
1214 /// Non-empty `data_field` sets GPO pin states. Empty data requests current
1215 /// GPO status in the response payload.
1216 pub fn set_gpo(data_field: &[u8]) -> Result<Vec<u8>, ProtocolError> {
1217 build_host_frame(CommandCode::SetGpo.as_u8(), data_field)
1218 }
1219
1220 /// Build command `0x97` (Set Current Region).
1221 ///
1222 /// Selects working region code used by frequency/hopping constraints.
1223 ///
1224 /// # Examples
1225 /// ```rust
1226 /// use rfid_silion_compat::RegionCode;
1227 /// use rfid_silion_compat::command::HostCommand;
1228 ///
1229 /// let packet = HostCommand::set_current_region(RegionCode::NorthAmerica).unwrap();
1230 /// assert_eq!(packet, vec![0xFF, 0x01, 0x97, 0x01, 0x4B, 0xBC]);
1231 /// ```
1232 pub fn set_current_region(region_code: RegionCode) -> Result<Vec<u8>, ProtocolError> {
1233 build_host_frame(
1234 CommandCode::SetCurrentRegion.as_u8(),
1235 &[region_code.as_u8()],
1236 )
1237 }
1238
1239 /// Build command `0x9A` (Set Reader Configuration).
1240 ///
1241 /// Sets one reader key/value under `option` 0x01 format.
1242 pub fn set_reader_configuration(
1243 option: u8,
1244 key: u8,
1245 value: u8,
1246 ) -> Result<Vec<u8>, ProtocolError> {
1247 build_host_frame(
1248 CommandCode::SetReaderConfiguration.as_u8(),
1249 &[option, key, value],
1250 )
1251 }
1252
1253 /// Build command `0x9B` (Set Protocol Configuration).
1254 ///
1255 /// Sets protocol parameter values (session, target, Q, etc.) according to
1256 /// option/value presence required by the selected parameter.
1257 pub fn set_protocol_configuration(
1258 protocol_value: u8,
1259 parameter: u8,
1260 option: Option<u8>,
1261 value: Option<u8>,
1262 ) -> Result<Vec<u8>, ProtocolError> {
1263 let mut data = vec![protocol_value, parameter];
1264 if let Some(opt) = option {
1265 data.push(opt);
1266 }
1267 if let Some(v) = value {
1268 data.push(v);
1269 }
1270 build_host_frame(CommandCode::SetProtocolConfiguration.as_u8(), &data)
1271 }
1272
1273 /// Build command `0x61` (Get Antenna Ports).
1274 ///
1275 /// `option` selects which antenna view is requested (access pair,
1276 /// inventory pairs, powers, powers+settling, or connection states).
1277 pub fn get_antenna_ports(option: AntennaPortsOption) -> Result<Vec<u8>, ProtocolError> {
1278 build_host_frame(CommandCode::GetAntennaPorts.as_u8(), &[option.as_u8()])
1279 }
1280
1281 /// Build command `0x63` (Get Current Tag Protocol).
1282 ///
1283 /// Returns active tag protocol (currently GEN2 `0x0005`).
1284 pub fn get_current_tag_protocol() -> Result<Vec<u8>, ProtocolError> {
1285 build_host_frame(CommandCode::GetCurrentTagProtocol.as_u8(), &[])
1286 }
1287
1288 /// Build command `0x65` (Get Frequency Hopping).
1289 ///
1290 /// `None` requests the full hop table. `Some(0x01)` requests regulatory
1291 /// hopping time payload format.
1292 ///
1293 /// # Examples
1294 /// ```rust
1295 /// use rfid_silion_compat::command::HostCommand;
1296 ///
1297 /// let table_req = HostCommand::get_frequency_hopping(None).unwrap();
1298 /// assert_eq!(table_req, vec![0xFF, 0x00, 0x65, 0x1D, 0x6A]);
1299 ///
1300 /// let hop_time_req = HostCommand::get_frequency_hopping(Some(0x01)).unwrap();
1301 /// assert_eq!(hop_time_req, vec![0xFF, 0x01, 0x65, 0x01, 0xB9, 0xBC]);
1302 /// ```
1303 pub fn get_frequency_hopping(option: Option<u8>) -> Result<Vec<u8>, ProtocolError> {
1304 match option {
1305 Some(v) => build_host_frame(CommandCode::GetFrequencyHopping.as_u8(), &[v]),
1306 None => build_host_frame(CommandCode::GetFrequencyHopping.as_u8(), &[]),
1307 }
1308 }
1309
1310 /// Build command `0x66` (Get GPI).
1311 ///
1312 /// Returns input pin states ordered by pin number.
1313 pub fn get_gpi() -> Result<Vec<u8>, ProtocolError> {
1314 build_host_frame(CommandCode::GetGpi.as_u8(), &[])
1315 }
1316
1317 /// Build command `0x67` (Get Current Region).
1318 ///
1319 /// Returns active region code.
1320 pub fn get_current_region() -> Result<Vec<u8>, ProtocolError> {
1321 build_host_frame(CommandCode::GetCurrentRegion.as_u8(), &[])
1322 }
1323
1324 /// Build command `0x71` (Get Available Regions).
1325 ///
1326 /// Returns region codes supported by the connected reader firmware.
1327 pub fn get_available_regions() -> Result<Vec<u8>, ProtocolError> {
1328 build_host_frame(CommandCode::GetAvailableRegions.as_u8(), &[])
1329 }
1330
1331 /// Build command `0x6A` (Get Reader Configuration).
1332 ///
1333 /// Requests one key under a given option namespace.
1334 pub fn get_reader_configuration(option: u8, key: u8) -> Result<Vec<u8>, ProtocolError> {
1335 build_host_frame(CommandCode::GetReaderConfiguration.as_u8(), &[option, key])
1336 }
1337
1338 /// Build command `0x6B` (Get Protocol Configuration).
1339 ///
1340 /// Requests one protocol parameter for a selected protocol id.
1341 ///
1342 /// # Examples
1343 /// ```rust
1344 /// use rfid_silion_compat::command::HostCommand;
1345 ///
1346 /// // Protocol 0x05 (GEN2), parameter 0x00 (session)
1347 /// let packet = HostCommand::get_protocol_configuration(0x05, 0x00).unwrap();
1348 /// assert_eq!(packet, vec![0xFF, 0x02, 0x6B, 0x05, 0x00, 0x3A, 0x6F]);
1349 /// ```
1350 pub fn get_protocol_configuration(
1351 protocol_value: u8,
1352 parameter: u8,
1353 ) -> Result<Vec<u8>, ProtocolError> {
1354 build_host_frame(
1355 CommandCode::GetProtocolConfiguration.as_u8(),
1356 &[protocol_value, parameter],
1357 )
1358 }
1359
1360 /// Build command `0x72` (Get Current Temperature).
1361 ///
1362 /// Returns reader board temperature as one byte.
1363 pub fn get_current_temperature() -> Result<Vec<u8>, ProtocolError> {
1364 build_host_frame(CommandCode::GetCurrentTemperature.as_u8(), &[])
1365 }
1366
1367 /// Build command `0xAA` Start Async Inventory subcommand (`0xAA48`).
1368 ///
1369 /// `start` follows the documented subcommand data format:
1370 /// `MetadataFlags(2) | Option(1) | SearchFlags(2) | [AccessPassword(4)] |
1371 /// [SelectContent] | [EmbeddedCommandContent]`.
1372 ///
1373 /// # Examples
1374 /// ```rust
1375 /// use rfid_silion_compat::{
1376 /// EmbeddedReadTagData, InventoryEmbeddedCommandContent, InventorySearchFlags, MemBank,
1377 /// MetadataFlags,
1378 /// };
1379 /// use rfid_silion_compat::command::{AsyncInventoryStartData, HostCommand, InventoryOption};
1380 ///
1381 /// let search_flags = InventorySearchFlags::new()
1382 /// .with_async_heartbeat(true)
1383 /// .with_async_auto_stop(false)
1384 /// .with_embedded_command(true)
1385 /// .with_async_rest_ratio_steps(3)
1386 /// .unwrap();
1387 ///
1388 /// let start = AsyncInventoryStartData {
1389 /// metadata_flags: MetadataFlags::ALL,
1390 /// option: InventoryOption::default(),
1391 /// search_flags,
1392 /// access_password: None,
1393 /// select_content: None,
1394 /// embedded_command_content: Some(InventoryEmbeddedCommandContent::ReadTagData(
1395 /// EmbeddedReadTagData {
1396 /// read_membank: MemBank::Tid,
1397 /// read_address_words: 0,
1398 /// word_count: 2,
1399 /// },
1400 /// )),
1401 /// };
1402 ///
1403 /// let packet = HostCommand::async_start(&start).unwrap();
1404 /// assert_eq!(packet[0], 0xFF);
1405 /// assert_eq!(packet[2], 0xAA);
1406 /// ```
1407 pub fn async_start(start: &AsyncInventoryStartData) -> Result<Vec<u8>, ProtocolError> {
1408 let subcommand_data = start.encode();
1409 Self::async_inventory(AsyncSubcommandCode::Start, &subcommand_data)
1410 }
1411
1412 /// Build command `0xAA` Stop Async Inventory subcommand (`0xAA49`).
1413 ///
1414 /// # Examples
1415 /// ```rust
1416 /// use rfid_silion_compat::command::HostCommand;
1417 ///
1418 /// let packet = HostCommand::async_stop().unwrap();
1419 /// assert_eq!(
1420 /// packet,
1421 /// vec![
1422 /// 0xFF, 0x0E, 0xAA, 0x4D, 0x6F, 0x64, 0x75, 0x6C, 0x65, 0x74,
1423 /// 0x65, 0x63, 0x68, 0xAA, 0x49, 0xF3, 0xBB, 0x03, 0x91,
1424 /// ]
1425 /// );
1426 /// ```
1427 pub fn async_stop() -> Result<Vec<u8>, ProtocolError> {
1428 Self::async_inventory(AsyncSubcommandCode::Stop, &[])
1429 }
1430
1431 /// Build a generic `0xAA` asynchronous inventory command packet.
1432 ///
1433 /// This inserts the fixed marker (`Moduletech`), subcommand, subcommand
1434 /// payload, sub-CRC (8-bit sum), and terminator (`0xBB`) before wrapping the
1435 /// bytes in a normal host frame.
1436 pub fn async_inventory(
1437 subcommand: AsyncSubcommandCode,
1438 subcommand_data: &[u8],
1439 ) -> Result<Vec<u8>, ProtocolError> {
1440 let mut data = Vec::with_capacity(10 + 2 + subcommand_data.len() + 2);
1441 data.extend_from_slice(ASYNC_MARKER);
1442 push_u16_be(&mut data, subcommand as u16);
1443 data.extend_from_slice(subcommand_data);
1444 let sub_crc = subcommand_crc(subcommand as u16, subcommand_data);
1445 data.push(sub_crc);
1446 data.push(ASYNC_TERMINATOR);
1447 build_host_frame(CommandCode::AsynchronousInventory.as_u8(), &data)
1448 }
1449}