1use crate::async_proto::parse_async_payload_owned;
2use crate::client::{ClientError, ReaderClient};
3use crate::codes::{AntennaPortsOption, CommandCode, RegionCode};
4use crate::command::{
5 AntennaPortsConfiguration, AsyncInventoryStartData as CommandAsyncInventoryStartData,
6 AsyncSubcommandCode, InventoryOption, InventorySearchFlags, MemBank, MetadataFlags,
7 SelectContent, SelectMode, SelectOptionBits,
8};
9use crate::error::ProtocolError;
10use crate::parsers::{
11 AntennaPortsResponse, ProtocolConfigurationValue, ReaderConfigurationValue, RegulatoryHopTime,
12 RunPhase, SerialNumberInfo, TagEpcAndMetaData, VersionInfo, parse_antenna_ports_response,
13 parse_available_regions, parse_current_region, parse_current_tag_protocol,
14 parse_current_temperature, parse_frequency_hopping_table, parse_pin_states,
15 parse_protocol_configuration_value, parse_reader_configuration_value,
16 parse_regulatory_hop_time, parse_run_phase, parse_serial_number_info,
17 parse_single_tag_inventory_response, parse_tag_epc_and_meta_data, parse_version_info,
18};
19use crate::session::AsyncInventorySession;
20use crate::transport::ReaderTransport;
21
22pub struct SilionReader<T: ReaderTransport> {
24 client: ReaderClient<T>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29#[cfg_attr(feature = "web-serial", derive(serde::Serialize))]
30#[cfg_attr(feature = "web-serial", serde(tag = "kind", rename_all = "camelCase"))]
31pub enum AsyncInventoryMessage {
32 StartAck,
34 StopAck,
36 TagInformation {
38 metadata_flags: MetadataFlags,
40 tag: TagEpcAndMetaData,
42 },
43 Heartbeat {
45 search_flags: InventorySearchFlags,
47 state_data: Vec<u8>,
49 },
50 Subcommand {
52 subcommand: u16,
54 subcommand_data: Vec<u8>,
56 },
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60#[cfg_attr(feature = "web-serial", derive(serde::Serialize))]
61#[cfg_attr(feature = "web-serial", derive(serde::Deserialize))]
62#[cfg_attr(feature = "web-serial", serde(tag = "type", rename_all = "camelCase"))]
63pub enum SelectOption {
65 Disabled,
67 Epc {
69 select_length_bits: u16,
71 select_data: Vec<u8>,
73 invert: bool,
75 },
76 Tid {
78 select_address: u32,
80 select_length_bits: u16,
82 select_data: Vec<u8>,
84 invert: bool,
86 },
87 UserMemory {
89 select_address: u32,
91 select_length_bits: u16,
93 select_data: Vec<u8>,
95 invert: bool,
97 },
98 EpcBank {
100 select_address: u32,
102 select_length_bits: u16,
104 select_data: Vec<u8>,
106 invert: bool,
108 },
109 PasswordOnly,
111}
112
113impl SelectOption {
114 fn into_option_content(self) -> (InventoryOption, Option<SelectContent>) {
115 match self {
116 SelectOption::Disabled => (InventoryOption::default(), None),
117 SelectOption::Epc {
118 select_length_bits,
119 select_data,
120 invert,
121 } => (
122 SelectOptionBits::new(SelectMode::Epc)
123 .with_invert_flag(invert)
124 .with_extended_data_length(select_length_bits > 255)
125 .into(),
126 Some(SelectContent {
127 address_bits: 0,
128 bit_len: select_length_bits,
129 data: select_data,
130 }),
131 ),
132 SelectOption::Tid {
133 select_address,
134 select_length_bits,
135 select_data,
136 invert,
137 } => (
138 SelectOptionBits::new(SelectMode::Tid)
139 .with_invert_flag(invert)
140 .with_extended_data_length(select_length_bits > 255)
141 .into(),
142 Some(SelectContent {
143 address_bits: select_address,
144 bit_len: select_length_bits,
145 data: select_data,
146 }),
147 ),
148 SelectOption::UserMemory {
149 select_address,
150 select_length_bits,
151 select_data,
152 invert,
153 } => (
154 SelectOptionBits::new(SelectMode::UserMemory)
155 .with_invert_flag(invert)
156 .with_extended_data_length(select_length_bits > 255)
157 .into(),
158 Some(SelectContent {
159 address_bits: select_address,
160 bit_len: select_length_bits,
161 data: select_data,
162 }),
163 ),
164 SelectOption::EpcBank {
165 select_address,
166 select_length_bits,
167 select_data,
168 invert,
169 } => (
170 SelectOptionBits::new(SelectMode::EpcBank)
171 .with_invert_flag(invert)
172 .with_extended_data_length(select_length_bits > 255)
173 .into(),
174 Some(SelectContent {
175 address_bits: select_address,
176 bit_len: select_length_bits,
177 data: select_data,
178 }),
179 ),
180 SelectOption::PasswordOnly => {
181 (SelectOptionBits::new(SelectMode::PasswordOnly).into(), None)
182 }
183 }
184 }
185}
186
187#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct ReaderAsyncInventoryStartData {
193 pub metadata_flags: MetadataFlags,
195 pub select_option: SelectOption,
197 pub search_flags: InventorySearchFlags,
199 pub access_password: Option<u32>,
201 pub embedded_command_content: Option<crate::InventoryEmbeddedCommandContent>,
203}
204
205impl ReaderAsyncInventoryStartData {
206 fn to_command_data(&self) -> CommandAsyncInventoryStartData {
207 let (option, select_content) = self.select_option.clone().into_option_content();
208 CommandAsyncInventoryStartData {
209 metadata_flags: self.metadata_flags,
210 option,
211 search_flags: self.search_flags,
212 access_password: self.access_password,
213 select_content,
214 embedded_command_content: self.embedded_command_content.clone(),
215 }
216 }
217}
218
219impl<T: ReaderTransport> SilionReader<T> {
220 pub fn new(transport: T) -> Self {
222 Self {
223 client: ReaderClient::new(transport),
224 }
225 }
226
227 pub fn from_client(client: ReaderClient<T>) -> Self {
229 Self { client }
230 }
231
232 pub fn into_inner(self) -> T {
234 self.client.into_inner()
235 }
236
237 pub fn transport_mut(&mut self) -> &mut T {
239 self.client.transport_mut()
240 }
241
242 pub fn into_client(self) -> ReaderClient<T> {
244 self.client
245 }
246
247 pub async fn transact_raw(
252 &mut self,
253 command: u8,
254 data: &[u8],
255 ) -> Result<crate::ReaderFrame, ClientError<T::Error>> {
256 self.client.transact(command, data).await
257 }
258
259 pub fn into_async_session(self) -> AsyncInventorySession<T> {
261 AsyncInventorySession::new(self.client)
262 }
263
264 pub async fn get_version(&mut self) -> Result<VersionInfo, ClientError<T::Error>> {
266 let frame = self
267 .client
268 .transact(CommandCode::GetVersion as u8, &[])
269 .await?;
270 parse_version_info(&frame.data).map_err(ClientError::Protocol)
271 }
272
273 pub async fn boot_firmware(&mut self) -> Result<(), ClientError<T::Error>> {
275 let _ = self
276 .client
277 .transact(CommandCode::BootFirmware as u8, &[])
278 .await?;
279 Ok(())
280 }
281
282 pub async fn boot_bootloader(&mut self) -> Result<(), ClientError<T::Error>> {
284 let _ = self
285 .client
286 .transact(CommandCode::BootBootloader as u8, &[])
287 .await?;
288 Ok(())
289 }
290
291 pub async fn get_run_phase(&mut self) -> Result<RunPhase, ClientError<T::Error>> {
293 let frame = self
294 .client
295 .transact(CommandCode::GetRunPhase as u8, &[])
296 .await?;
297 parse_run_phase(&frame.data).map_err(ClientError::Protocol)
298 }
299
300 pub async fn get_serial_number(
302 &mut self,
303 option: u8,
304 data_flags: u8,
305 ) -> Result<SerialNumberInfo, ClientError<T::Error>> {
306 let frame = self
307 .client
308 .transact(CommandCode::GetSerialNumber as u8, &[option, data_flags])
309 .await?;
310 parse_serial_number_info(&frame.data).map_err(ClientError::Protocol)
311 }
312
313 pub async fn get_current_tag_protocol(&mut self) -> Result<u16, ClientError<T::Error>> {
315 let frame = self
316 .client
317 .transact(CommandCode::GetCurrentTagProtocol as u8, &[])
318 .await?;
319 parse_current_tag_protocol(&frame.data).map_err(ClientError::Protocol)
320 }
321
322 pub async fn set_current_region(
324 &mut self,
325 region_code: RegionCode,
326 ) -> Result<(), ClientError<T::Error>> {
327 let _ = self
328 .client
329 .transact(CommandCode::SetCurrentRegion as u8, &[region_code.as_u8()])
330 .await?;
331 Ok(())
332 }
333
334 pub async fn get_current_region(&mut self) -> Result<RegionCode, ClientError<T::Error>> {
336 let frame = self
337 .client
338 .transact(CommandCode::GetCurrentRegion as u8, &[])
339 .await?;
340 parse_current_region(&frame.data).map_err(ClientError::Protocol)
341 }
342
343 pub async fn get_available_regions(
345 &mut self,
346 ) -> Result<Vec<RegionCode>, ClientError<T::Error>> {
347 let frame = self
348 .client
349 .transact(CommandCode::GetAvailableRegions as u8, &[])
350 .await?;
351 parse_available_regions(&frame.data).map_err(ClientError::Protocol)
352 }
353
354 pub async fn get_current_temperature(&mut self) -> Result<u8, ClientError<T::Error>> {
356 let frame = self
357 .client
358 .transact(CommandCode::GetCurrentTemperature as u8, &[])
359 .await?;
360 parse_current_temperature(&frame.data).map_err(ClientError::Protocol)
361 }
362
363 pub async fn get_gpi(&mut self) -> Result<Vec<u8>, ClientError<T::Error>> {
365 let frame = self.client.transact(CommandCode::GetGpi as u8, &[]).await?;
366 parse_pin_states(&frame.data).map_err(ClientError::Protocol)
367 }
368
369 pub async fn get_gpo_states(&mut self) -> Result<Vec<u8>, ClientError<T::Error>> {
371 let frame = self.client.transact(CommandCode::SetGpo as u8, &[]).await?;
372 parse_pin_states(&frame.data).map_err(ClientError::Protocol)
373 }
374
375 pub async fn set_antenna_ports(
377 &mut self,
378 config: &AntennaPortsConfiguration,
379 ) -> Result<(), ClientError<T::Error>> {
380 let packet = crate::command::HostCommand::set_antenna_ports(config)
381 .map_err(ClientError::Protocol)?;
382 let _ = self.client.transact_frame(&packet).await?;
383 Ok(())
384 }
385
386 pub async fn enable_async_inventory(
388 &mut self,
389 start: &ReaderAsyncInventoryStartData,
390 ) -> Result<(), ClientError<T::Error>> {
391 let command_data = start.to_command_data();
392 let packet = crate::command::HostCommand::async_start(&command_data)
393 .map_err(ClientError::Protocol)?;
394 let response = self.client.transact_frame(&packet).await?;
395 if response.command != CommandCode::AsynchronousInventory as u8 {
396 return Err(ClientError::UnexpectedResponseCommand {
397 expected: CommandCode::AsynchronousInventory as u8,
398 actual: response.command,
399 });
400 }
401 if response.status_raw != 0x0000 {
402 return Err(ClientError::ReaderStatus {
403 status_raw: response.status_raw,
404 status: response.status,
405 });
406 }
407 Ok(())
408 }
409
410 pub async fn disable_async_inventory(&mut self) -> Result<(), ClientError<T::Error>> {
412 let packet = crate::command::HostCommand::async_stop().map_err(ClientError::Protocol)?;
413 let response = self.client.transact_frame(&packet).await?;
414 if response.command != CommandCode::AsynchronousInventory as u8 {
415 return Err(ClientError::UnexpectedResponseCommand {
416 expected: CommandCode::AsynchronousInventory as u8,
417 actual: response.command,
418 });
419 }
420 if response.status_raw != 0x0000 {
421 return Err(ClientError::ReaderStatus {
422 status_raw: response.status_raw,
423 status: response.status,
424 });
425 }
426 Ok(())
427 }
428
429 pub async fn recv_async_inventory_message(
431 &mut self,
432 ) -> Result<AsyncInventoryMessage, ClientError<T::Error>> {
433 let frame = self.client.read_frame().await?;
434 if frame.command != CommandCode::AsynchronousInventory as u8 {
435 return Err(ClientError::UnexpectedResponseCommand {
436 expected: CommandCode::AsynchronousInventory as u8,
437 actual: frame.command,
438 });
439 }
440 if frame.status_raw != 0x0000 {
441 return Err(ClientError::ReaderStatus {
442 status_raw: frame.status_raw,
443 status: frame.status,
444 });
445 }
446 parse_async_frame_data(&frame.data).map_err(ClientError::Protocol)
447 }
448
449 pub async fn single_tag_inventory(
463 &mut self,
464 timeout_ms: u16,
465 select_option: SelectOption,
466 metadata_flags: Option<MetadataFlags>,
467 ) -> Result<TagEpcAndMetaData, ClientError<T::Error>> {
468 let (option, select_content) = select_option.into_option_content();
470 let option = option.with_single_tag_metadata(metadata_flags.is_some());
471
472 let packet = crate::command::HostCommand::single_tag_inventory(
473 timeout_ms,
474 option,
475 metadata_flags,
476 select_content,
477 )
478 .map_err(ClientError::Protocol)?;
479
480 let frame = self.client.transact_frame(&packet).await?;
481 if frame.command != CommandCode::SingleTagInventory as u8 {
482 return Err(ClientError::UnexpectedResponseCommand {
483 expected: CommandCode::SingleTagInventory as u8,
484 actual: frame.command,
485 });
486 }
487 if frame.status_raw != 0x0000 {
488 return Err(ClientError::ReaderStatus {
489 status_raw: frame.status_raw,
490 status: frame.status,
491 });
492 }
493
494 let (_response_metadata_flags, tag) =
495 parse_single_tag_inventory_response(option.raw(), &frame.data)
496 .map_err(ClientError::Protocol)?;
497 Ok(tag)
498 }
499
500 pub async fn read_tag_data(
517 &mut self,
518 timeout_ms: u16,
519 select_option: SelectOption,
520 metadata_flags: Option<MetadataFlags>,
521 read_membank: MemBank,
522 read_address_words: u32,
523 word_count: u8,
524 ) -> Result<TagEpcAndMetaData, ClientError<T::Error>> {
525 let (option, select_content) = select_option.into_option_content();
526 let option = option.with_single_tag_metadata(metadata_flags.is_some());
527
528 let packet = crate::command::HostCommand::read_tag_data(
529 timeout_ms,
530 option,
531 metadata_flags,
532 read_membank,
533 read_address_words,
534 word_count,
535 None,
536 select_content,
537 )
538 .map_err(ClientError::Protocol)?;
539
540 let frame = self.client.transact_frame(&packet).await?;
541 if frame.command != CommandCode::ReadTagData as u8 {
542 return Err(ClientError::UnexpectedResponseCommand {
543 expected: CommandCode::ReadTagData as u8,
544 actual: frame.command,
545 });
546 }
547 if frame.status_raw != 0x0000 {
548 return Err(ClientError::ReaderStatus {
549 status_raw: frame.status_raw,
550 status: frame.status,
551 });
552 }
553
554 let returned_options = InventoryOption::from_raw(frame.data[0]);
555 if returned_options.single_tag_metadata_enabled() {
556 return parse_tag_epc_and_meta_data(
557 metadata_flags.unwrap_or(MetadataFlags::NONE),
558 &frame.data,
559 )
560 .map_err(ClientError::Protocol);
561 } else {
562 let frame_data = frame.data[1..].to_vec();
563 return Ok(TagEpcAndMetaData {
564 read_count: None,
565 rssi_dbm: None,
566 antenna_id: None,
567 frequency_khz: None,
568 timestamp_ms: None,
569 rfu: None,
570 protocol_id: None,
571 tag_data_bit_length: Some(frame_data.len() as u16 * 8),
572 tag_data: Some(frame_data),
573 epc_bit_length: None,
574 pc_word: None,
575 epc_id: Vec::new(),
576 tag_crc: 0,
577 });
578 }
579 }
580
581 pub async fn write_tag_epc(
593 &mut self,
594 timeout_ms: u16,
595 select_option: SelectOption,
596 access_password: Option<u32>,
597 epc: &[u8],
598 ) -> Result<(), ClientError<T::Error>> {
599 let (option, select_content) = select_option.into_option_content();
600 let packet = crate::command::HostCommand::write_tag_epc(
601 timeout_ms,
602 option,
603 access_password,
604 select_content,
605 epc,
606 )
607 .map_err(ClientError::Protocol)?;
608
609 let frame = self.client.transact_frame(&packet).await?;
610 if frame.command != CommandCode::WriteTagEpc as u8 {
611 return Err(ClientError::UnexpectedResponseCommand {
612 expected: CommandCode::WriteTagEpc as u8,
613 actual: frame.command,
614 });
615 }
616 if frame.status_raw != 0x0000 {
617 return Err(ClientError::ReaderStatus {
618 status_raw: frame.status_raw,
619 status: frame.status,
620 });
621 }
622 Ok(())
623 }
624
625 pub async fn write_tag_data(
639 &mut self,
640 timeout_ms: u16,
641 select_option: SelectOption,
642 write_address_words: u32,
643 write_membank: MemBank,
644 access_password: Option<u32>,
645 write_data: &[u8],
646 ) -> Result<(), ClientError<T::Error>> {
647 let (option, select_content) = select_option.into_option_content();
648 let packet = crate::command::HostCommand::write_tag_data(
649 timeout_ms,
650 option,
651 write_address_words,
652 write_membank,
653 access_password,
654 select_content,
655 write_data,
656 )
657 .map_err(ClientError::Protocol)?;
658
659 let frame = self.client.transact_frame(&packet).await?;
660 if frame.command != CommandCode::WriteTagData as u8 {
661 return Err(ClientError::UnexpectedResponseCommand {
662 expected: CommandCode::WriteTagData as u8,
663 actual: frame.command,
664 });
665 }
666 if frame.status_raw != 0x0000 {
667 return Err(ClientError::ReaderStatus {
668 status_raw: frame.status_raw,
669 status: frame.status,
670 });
671 }
672 Ok(())
673 }
674
675 pub async fn get_frequency_hopping_table(&mut self) -> Result<Vec<u32>, ClientError<T::Error>> {
677 let frame = self
678 .client
679 .transact(CommandCode::GetFrequencyHopping as u8, &[])
680 .await?;
681 parse_frequency_hopping_table(&frame.data).map_err(ClientError::Protocol)
682 }
683
684 pub async fn get_regulatory_hop_time(
686 &mut self,
687 ) -> Result<RegulatoryHopTime, ClientError<T::Error>> {
688 let frame = self
689 .client
690 .transact(CommandCode::GetFrequencyHopping as u8, &[0x01])
691 .await?;
692 parse_regulatory_hop_time(&frame.data).map_err(ClientError::Protocol)
693 }
694
695 pub async fn get_antenna_ports(
697 &mut self,
698 option: AntennaPortsOption,
699 ) -> Result<AntennaPortsResponse, ClientError<T::Error>> {
700 let frame = self
701 .client
702 .transact(CommandCode::GetAntennaPorts as u8, &[option.as_u8()])
703 .await?;
704 parse_antenna_ports_response(option, &frame.data).map_err(ClientError::Protocol)
705 }
706
707 pub async fn get_reader_configuration(
709 &mut self,
710 option: u8,
711 key: u8,
712 ) -> Result<ReaderConfigurationValue, ClientError<T::Error>> {
713 let frame = self
714 .client
715 .transact(CommandCode::GetReaderConfiguration as u8, &[option, key])
716 .await?;
717 parse_reader_configuration_value(&frame.data).map_err(ClientError::Protocol)
718 }
719
720 pub async fn get_protocol_configuration(
722 &mut self,
723 protocol_value: u8,
724 parameter: u8,
725 ) -> Result<ProtocolConfigurationValue, ClientError<T::Error>> {
726 let frame = self
727 .client
728 .transact(
729 CommandCode::GetProtocolConfiguration as u8,
730 &[protocol_value, parameter],
731 )
732 .await?;
733 parse_protocol_configuration_value(&frame.data).map_err(ClientError::Protocol)
734 }
735}
736
737pub(crate) fn parse_async_frame_data(data: &[u8]) -> Result<AsyncInventoryMessage, ProtocolError> {
740 if data.starts_with(b"Moduletech") {
741 let payload = parse_async_payload_owned(data)?;
742 return Ok(match payload.subcommand {
743 x if x == AsyncSubcommandCode::Start as u16 => AsyncInventoryMessage::StartAck,
744 x if x == AsyncSubcommandCode::Stop as u16 => AsyncInventoryMessage::StopAck,
745 _ => AsyncInventoryMessage::Subcommand {
746 subcommand: payload.subcommand,
747 subcommand_data: payload.subcommand_data,
748 },
749 });
750 }
751
752 if data.starts_with(b"XTSJ") {
753 if data.len() < 6 {
754 return Err(ProtocolError::InvalidResponse(
755 "heartbeat payload too short",
756 ));
757 }
758 let search_flags = InventorySearchFlags::from_raw(u16::from_be_bytes([data[4], data[5]]));
759 return Ok(AsyncInventoryMessage::Heartbeat {
760 search_flags,
761 state_data: data[6..].to_vec(),
762 });
763 }
764
765 if data.len() < 2 {
766 return Err(ProtocolError::InvalidResponse(
767 "tag information payload too short",
768 ));
769 }
770
771 let metadata_flags = MetadataFlags::from_raw(u16::from_be_bytes([data[0], data[1]]));
772
773 Ok(AsyncInventoryMessage::TagInformation {
774 metadata_flags,
775 tag: parse_tag_epc_and_meta_data(metadata_flags, &data[2..])?,
776 })
777}
778
779#[cfg(test)]
780mod tests {
781 use super::SilionReader;
782 use crate::codes::CommandCode;
783 use crate::test_support::{MockInteraction, MockTransport, reply_frame};
784 use crate::{AsyncInventoryMessage, InventorySearchFlags, RegionCode};
785
786 #[test]
787 fn get_version_and_region() {
788 let version_data = vec![
789 0x13, 0x04, 0x15, 0x00, 0xA8, 0x00, 0x00, 0x01, 0x20, 0x13, 0x05, 0x22, 0x13, 0x05,
790 0x23, 0x00, 0x00, 0x00, 0x00, 0x10,
791 ];
792 let transport = MockTransport::scripted(vec![
793 MockInteraction {
794 request_command: CommandCode::GetVersion as u8,
795 response_status: 0x0000,
796 response_data: version_data,
797 },
798 MockInteraction {
799 request_command: CommandCode::GetCurrentRegion as u8,
800 response_status: 0x0000,
801 response_data: vec![0x01],
802 },
803 ]);
804
805 let mut reader = SilionReader::new(transport);
806 let version =
807 futures::executor::block_on(reader.get_version()).expect("version should parse");
808 assert_eq!(version.supported_protocol, [0x00, 0x00, 0x00, 0x10]);
809
810 let region =
811 futures::executor::block_on(reader.get_current_region()).expect("region should parse");
812 assert_eq!(region, RegionCode::NorthAmerica);
813 }
814
815 #[test]
816 fn recv_async_inventory_heartbeat() {
817 let mut data = b"XTSJ".to_vec();
818 data.extend_from_slice(&0x8000u16.to_be_bytes());
819 data.push(0x01);
820 let packet = reply_frame(0xAA, 0x0000, &data);
821 let transport = MockTransport::from_replies(vec![packet]);
822
823 let mut reader = SilionReader::new(transport);
824 let message = futures::executor::block_on(reader.recv_async_inventory_message())
825 .expect("async message should parse");
826
827 match message {
828 AsyncInventoryMessage::Heartbeat {
829 search_flags,
830 state_data,
831 } => {
832 assert_eq!(search_flags, InventorySearchFlags::from_raw(0x8000));
833 assert_eq!(state_data, vec![0x01]);
834 }
835 other => panic!("unexpected async message: {other:?}"),
836 }
837 }
838
839 #[test]
840 #[cfg(feature = "web-serial")]
841 fn tag_information_serialization() {
842 use crate::command::MetadataFlags;
843 use crate::parsers::TagEpcAndMetaData;
844
845 let tag = TagEpcAndMetaData {
846 read_count: Some(1),
847 rssi_dbm: Some(-50),
848 antenna_id: Some(1),
849 frequency_khz: Some(902250),
850 timestamp_ms: Some(1000),
851 rfu: None,
852 protocol_id: Some(5),
853 tag_data_bit_length: None,
854 tag_data: None,
855 epc_bit_length: 96,
856 pc_word: 0x3000,
857 epc_id: vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
858 tag_crc: 0x1234,
859 };
860
861 let msg = AsyncInventoryMessage::TagInformation {
862 metadata_flags: MetadataFlags::from_raw(0x00FF),
863 tag,
864 };
865
866 let json_str = serde_json::to_string(&msg).expect("should serialize to JSON");
867 let json_value: serde_json::Value =
868 serde_json::from_str(&json_str).expect("should parse JSON");
869
870 assert_eq!(
872 json_value.get("kind").and_then(|v| v.as_str()),
873 Some("tagInformation")
874 );
875
876 let tag_obj = json_value
878 .get("tag")
879 .and_then(|v| v.as_object())
880 .expect("tag should be an object");
881 assert!(
882 tag_obj.get("epcId").is_some(),
883 "tag.epcId should be present"
884 );
885 assert!(
886 tag_obj.get("readCount").is_some(),
887 "tag.readCount should be present"
888 );
889 assert!(
890 tag_obj.get("rssiDbm").is_some(),
891 "tag.rssiDbm should be present"
892 );
893 assert!(
894 tag_obj.get("antennaId").is_some(),
895 "tag.antennaId should be present"
896 );
897 assert!(
898 tag_obj.get("frequencyKhz").is_some(),
899 "tag.frequencyKhz should be present"
900 );
901 assert!(
902 tag_obj.get("timestampMs").is_some(),
903 "tag.timestampMs should be present"
904 );
905 assert!(
906 tag_obj.get("protocolId").is_some(),
907 "tag.protocolId should be present"
908 );
909 assert!(
910 tag_obj.get("epcBitLength").is_some(),
911 "tag.epcBitLength should be present"
912 );
913 assert!(
914 tag_obj.get("pcWord").is_some(),
915 "tag.pcWord should be present"
916 );
917 assert!(
918 tag_obj.get("tagCrc").is_some(),
919 "tag.tagCrc should be present"
920 );
921 }
922
923 #[test]
924 fn single_tag_inventory_success() {
925 use crate::command::MetadataFlags;
926 use crate::silion_reader::SelectOption;
927
928 let mut response_data = Vec::new();
931
932 response_data.push(0x10);
934 response_data.extend_from_slice(&0x0000u16.to_be_bytes());
936
937 response_data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
939
940 response_data.extend_from_slice(&0x1234u16.to_be_bytes());
942
943 let transport = MockTransport::from_replies(vec![reply_frame(
944 CommandCode::SingleTagInventory as u8,
945 0x0000,
946 &response_data,
947 )]);
948
949 let mut reader = SilionReader::new(transport);
950 let tag = futures::executor::block_on(reader.single_tag_inventory(
951 5000, SelectOption::Disabled, Some(MetadataFlags::from_raw(0x0000)), ))
955 .expect("single tag inventory should succeed");
956
957 assert_eq!(tag.pc_word, Some(0x0000));
958 assert_eq!(tag.epc_bit_length, Some(48));
959 assert_eq!(tag.epc_id, vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
960 assert_eq!(tag.tag_crc, 0x1234);
961 assert_eq!(tag.read_count, None); assert_eq!(tag.rssi_dbm, None); }
964}