Skip to main content

rfid_silion_compat/
session.rs

1use 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
8/// An active asynchronous inventory session driven by awaited reads.
9///
10/// Created by [`SilionReader::into_async_session`]. The transport is moved into
11/// this session for the duration of asynchronous inventory, and no other
12/// commands can be sent until [`stop`][Self::stop] is called and the transport
13/// is recovered as a [`SilionReader`].
14pub 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    /// Receive one pushed asynchronous inventory message from the reader.
24    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    /// Send `0xAA49`, wait for `StopAck`, and recover the reader.
42    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    /// Send `0xAA49` and recover the host without waiting for `StopAck`.
57    ///
58    /// This is useful for environments where an in-flight async receive was
59    /// canceled and waiting for the acknowledgement could otherwise block
60    /// indefinitely.
61    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}