use crate::ClientError;
use crate::client::ReaderClient;
use crate::codes::CommandCode;
use crate::command::HostCommand;
use crate::silion_reader::{AsyncInventoryMessage, SilionReader, parse_async_frame_data};
use crate::transport::ReaderTransport;
pub struct AsyncInventorySession<T: ReaderTransport> {
client: ReaderClient<T>,
}
impl<T: ReaderTransport> AsyncInventorySession<T> {
pub(crate) fn new(client: ReaderClient<T>) -> Self {
Self { client }
}
pub async fn recv(&mut self) -> Result<AsyncInventoryMessage, ClientError<T::Error>> {
let frame = self.client.read_frame().await?;
if frame.command != CommandCode::AsynchronousInventory as u8 {
return Err(ClientError::UnexpectedResponseCommand {
expected: CommandCode::AsynchronousInventory as u8,
actual: frame.command,
});
}
if frame.status_raw != 0x0000 {
return Err(ClientError::ReaderStatus {
status_raw: frame.status_raw,
status: frame.status,
});
}
parse_async_frame_data(&frame.data).map_err(ClientError::Protocol)
}
pub async fn stop(mut self) -> Result<SilionReader<T>, ClientError<T::Error>> {
let stop_packet = HostCommand::async_stop().map_err(ClientError::Protocol)?;
self.client.write_frame(&stop_packet).await?;
loop {
let message = self.recv().await?;
if matches!(message, AsyncInventoryMessage::StopAck) {
break;
}
}
Ok(SilionReader::from_client(self.client))
}
pub async fn stop_no_wait(mut self) -> Result<SilionReader<T>, ClientError<T::Error>> {
let stop_packet = HostCommand::async_stop().map_err(ClientError::Protocol)?;
self.client.write_frame(&stop_packet).await?;
Ok(SilionReader::from_client(self.client))
}
}
#[cfg(test)]
mod tests {
use super::AsyncInventorySession;
use crate::client::ReaderClient;
use crate::codes::CommandCode;
use crate::command::AsyncSubcommandCode;
use crate::test_support::{MockInteraction, MockTransport, reply_frame};
use crate::{
AsyncInventoryMessage, InventorySearchFlags, RegionCode, async_proto::subcommand_crc,
};
#[test]
fn recv_heartbeat_message() {
let mut data = b"XTSJ".to_vec();
data.extend_from_slice(&0x8000u16.to_be_bytes());
data.push(0x01);
let packet = reply_frame(CommandCode::AsynchronousInventory as u8, 0x0000, &data);
let transport = MockTransport::from_replies(vec![packet]);
let client = ReaderClient::new(transport);
let mut session = AsyncInventorySession::new(client);
let message = futures::executor::block_on(session.recv()).expect("message should parse");
match message {
AsyncInventoryMessage::Heartbeat {
search_flags,
state_data,
} => {
assert_eq!(search_flags, InventorySearchFlags::from_raw(0x8000));
assert_eq!(state_data, vec![0x01]);
}
other => panic!("unexpected async message: {other:?}"),
}
}
#[test]
fn stop_recovers_reader() {
let mut stop_ack = b"Moduletech".to_vec();
stop_ack.extend_from_slice(&(AsyncSubcommandCode::Stop as u16).to_be_bytes());
stop_ack.push(subcommand_crc(AsyncSubcommandCode::Stop as u16, &[]));
stop_ack.push(0xBB);
let transport = MockTransport::scripted(vec![
MockInteraction {
request_command: CommandCode::AsynchronousInventory as u8,
response_status: 0x0000,
response_data: stop_ack,
},
MockInteraction {
request_command: CommandCode::GetCurrentRegion as u8,
response_status: 0x0000,
response_data: vec![0x01],
},
]);
let client = ReaderClient::new(transport);
let session = AsyncInventorySession::new(client);
let mut reader =
futures::executor::block_on(session.stop()).expect("stop should recover reader");
let region = futures::executor::block_on(reader.get_current_region())
.expect("recovered reader should work");
assert_eq!(region, RegionCode::NorthAmerica);
}
}