rfid_silion_compat/
session.rs1use crate::ClientError;
2use crate::client::ReaderClient;
3use crate::codes::CommandCode;
4use crate::command::HostCommand;
5use crate::silion_reader::{AsyncInventoryMessage, SilionReader, parse_async_frame_data};
6use crate::transport::ReaderTransport;
7
8pub struct AsyncInventorySession<T: ReaderTransport> {
15 client: ReaderClient<T>,
16}
17
18impl<T: ReaderTransport> AsyncInventorySession<T> {
19 pub(crate) fn new(client: ReaderClient<T>) -> Self {
20 Self { client }
21 }
22
23 pub async fn recv(&mut self) -> Result<AsyncInventoryMessage, ClientError<T::Error>> {
25 let frame = self.client.read_frame().await?;
26 if frame.command != CommandCode::AsynchronousInventory as u8 {
27 return Err(ClientError::UnexpectedResponseCommand {
28 expected: CommandCode::AsynchronousInventory as u8,
29 actual: frame.command,
30 });
31 }
32 if frame.status_raw != 0x0000 {
33 return Err(ClientError::ReaderStatus {
34 status_raw: frame.status_raw,
35 status: frame.status,
36 });
37 }
38 parse_async_frame_data(&frame.data).map_err(ClientError::Protocol)
39 }
40
41 pub async fn stop(mut self) -> Result<SilionReader<T>, ClientError<T::Error>> {
43 let stop_packet = HostCommand::async_stop().map_err(ClientError::Protocol)?;
44 self.client.write_frame(&stop_packet).await?;
45
46 loop {
47 let message = self.recv().await?;
48 if matches!(message, AsyncInventoryMessage::StopAck) {
49 break;
50 }
51 }
52
53 Ok(SilionReader::from_client(self.client))
54 }
55
56 pub async fn stop_no_wait(mut self) -> Result<SilionReader<T>, ClientError<T::Error>> {
62 let stop_packet = HostCommand::async_stop().map_err(ClientError::Protocol)?;
63 self.client.write_frame(&stop_packet).await?;
64 Ok(SilionReader::from_client(self.client))
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::AsyncInventorySession;
71 use crate::client::ReaderClient;
72 use crate::codes::CommandCode;
73 use crate::command::AsyncSubcommandCode;
74 use crate::test_support::{MockInteraction, MockTransport, reply_frame};
75 use crate::{
76 AsyncInventoryMessage, InventorySearchFlags, RegionCode, async_proto::subcommand_crc,
77 };
78
79 #[test]
80 fn recv_heartbeat_message() {
81 let mut data = b"XTSJ".to_vec();
82 data.extend_from_slice(&0x8000u16.to_be_bytes());
83 data.push(0x01);
84 let packet = reply_frame(CommandCode::AsynchronousInventory as u8, 0x0000, &data);
85 let transport = MockTransport::from_replies(vec![packet]);
86 let client = ReaderClient::new(transport);
87 let mut session = AsyncInventorySession::new(client);
88
89 let message = futures::executor::block_on(session.recv()).expect("message should parse");
90 match message {
91 AsyncInventoryMessage::Heartbeat {
92 search_flags,
93 state_data,
94 } => {
95 assert_eq!(search_flags, InventorySearchFlags::from_raw(0x8000));
96 assert_eq!(state_data, vec![0x01]);
97 }
98 other => panic!("unexpected async message: {other:?}"),
99 }
100 }
101
102 #[test]
103 fn stop_recovers_reader() {
104 let mut stop_ack = b"Moduletech".to_vec();
105 stop_ack.extend_from_slice(&(AsyncSubcommandCode::Stop as u16).to_be_bytes());
106 stop_ack.push(subcommand_crc(AsyncSubcommandCode::Stop as u16, &[]));
107 stop_ack.push(0xBB);
108
109 let transport = MockTransport::scripted(vec![
110 MockInteraction {
111 request_command: CommandCode::AsynchronousInventory as u8,
112 response_status: 0x0000,
113 response_data: stop_ack,
114 },
115 MockInteraction {
116 request_command: CommandCode::GetCurrentRegion as u8,
117 response_status: 0x0000,
118 response_data: vec![0x01],
119 },
120 ]);
121 let client = ReaderClient::new(transport);
122 let session = AsyncInventorySession::new(client);
123
124 let mut reader =
125 futures::executor::block_on(session.stop()).expect("stop should recover reader");
126 let region = futures::executor::block_on(reader.get_current_region())
127 .expect("recovered reader should work");
128 assert_eq!(region, RegionCode::NorthAmerica);
129 }
130}