1#![deny(missing_docs)]
2
3mod async_proto;
53mod client;
54pub mod codes;
56pub mod command;
58pub mod epc;
60mod error;
61pub mod frame;
63pub mod parsers;
65mod session;
66mod silion_reader;
67mod transport;
68
69#[doc(hidden)]
71pub mod test_support;
72
73#[cfg(feature = "serial")]
74pub mod serial;
76
77#[cfg(all(target_arch = "wasm32", feature = "web-serial"))]
78pub mod web_serial;
80
81#[cfg(all(target_arch = "wasm32", feature = "web-serial"))]
82pub mod web_bindings;
84
85pub use client::{ClientError, ReaderClient};
86pub use codes::{AntennaPortsOption, RegionCode, StatusCode};
87pub use command::{
88 AntennaPortsConfiguration, EmbeddedReadTagData, InventoryEmbeddedCommandContent,
89 InventorySearchFlags, MemBank, MetadataFlags,
90};
91pub use epc::{EpcSchema, EpcValue, Giai96, Sgtin96};
92pub use error::ProtocolError;
93pub use frame::ReaderFrame;
94pub use parsers::{
95 AntennaPair, AntennaPortsResponse, AntennaPower, AntennaPowerSettling,
96 ProtocolConfigurationValue, ReaderConfigurationValue, RegulatoryHopTime, RunPhase,
97 SerialNumberInfo, TagEpcAndMetaData, VersionInfo,
98};
99#[cfg(feature = "serial")]
100pub use serial::SerialTransport;
101pub use session::AsyncInventorySession;
102pub use silion_reader::{
103 AsyncInventoryMessage, ReaderAsyncInventoryStartData, SelectOption, SilionReader,
104};
105pub use transport::ReaderTransport;
106
107#[cfg(test)]
108mod tests {
109 use std::collections::VecDeque;
110
111 use crate::codes::CommandCode;
112 use crate::command::HostCommand;
113 use crate::frame::{parse_reader_frame, protocol_crc16};
114 use crate::parsers::{
115 parse_antenna_ports_response, parse_frequency_hopping_table, parse_run_phase,
116 parse_version_info,
117 };
118
119 use super::*;
120
121 #[test]
122 fn crc16_matches_doc_example_for_get_version() {
123 let msg = [0xFF, 0x00, 0x03];
124 assert_eq!(protocol_crc16(&msg), 0x1D0C);
125 }
126
127 #[test]
128 fn build_get_version_packet_matches_example() {
129 let p = HostCommand::get_version().unwrap();
130 assert_eq!(p, vec![0xFF, 0x00, 0x03, 0x1D, 0x0C]);
131 }
132
133 #[test]
134 fn parse_reader_frame_success() {
135 let packet = [0xFF, 0x01, 0x0C, 0x00, 0x00, 0x12, 0x63, 0x43];
136 let f = parse_reader_frame(&packet).unwrap();
137 assert_eq!(f.command, 0x0C);
138 assert_eq!(f.status_raw, 0x0000);
139 assert_eq!(f.status, Some(StatusCode::Success));
140 assert_eq!(f.data, vec![0x12]);
141 }
142
143 #[test]
144 fn build_set_current_region_packet() {
145 let p = HostCommand::set_current_region(RegionCode::NorthAmerica).unwrap();
146 assert_eq!(p, vec![0xFF, 0x01, 0x97, 0x01, 0x4B, 0xBC]);
147 }
148
149 #[test]
150 fn build_set_antenna_access_pair_packet() {
151 let p =
152 HostCommand::set_antenna_ports(&AntennaPortsConfiguration::AccessPair(AntennaPair {
153 tx: 0x01,
154 rx: 0x01,
155 }))
156 .unwrap();
157 assert_eq!(p, vec![0xFF, 0x03, 0x91, 0x00, 0x01, 0x01, 0x62, 0x87]);
158 }
159
160 #[test]
161 fn async_stop_matches_document_example() {
162 let p = HostCommand::async_stop().unwrap();
163 assert_eq!(
164 p,
165 vec![
166 0xFF, 0x0E, 0xAA, 0x4D, 0x6F, 0x64, 0x75, 0x6C, 0x65, 0x74, 0x65, 0x63, 0x68, 0xAA,
167 0x49, 0xF3, 0xBB, 0x03, 0x91,
168 ]
169 );
170 }
171
172 #[test]
173 fn parse_version_info_ok() {
174 let data = [
175 0x13, 0x04, 0x15, 0x00, 0xA8, 0x00, 0x00, 0x01, 0x20, 0x13, 0x05, 0x22, 0x13, 0x05,
176 0x23, 0x00, 0x00, 0x00, 0x00, 0x10,
177 ];
178 let v = parse_version_info(&data).unwrap();
179 assert_eq!(v.supported_protocol, [0x00, 0x00, 0x00, 0x10]);
180 }
181
182 #[test]
183 fn parse_get_run_phase() {
184 assert_eq!(parse_run_phase(&[0x11]).unwrap(), RunPhase::Bootloader);
185 assert_eq!(parse_run_phase(&[0x12]).unwrap(), RunPhase::AppFirmware);
186 }
187
188 #[test]
189 fn parse_get_frequency_hopping_table() {
190 let data = [0x00, 0x0D, 0xF7, 0x32, 0x00, 0x0D, 0xC8, 0x52];
191 let freqs = parse_frequency_hopping_table(&data).unwrap();
192 assert_eq!(freqs, vec![915_250, 903_250]);
193 }
194
195 #[test]
196 fn parse_get_antenna_ports_power() {
197 let data = [0x03, 0x01, 0x0B, 0xB8, 0x0B, 0xB8];
198 let parsed = parse_antenna_ports_response(AntennaPortsOption::Power, &data).unwrap();
199 assert_eq!(
200 parsed,
201 AntennaPortsResponse::Power(vec![AntennaPower {
202 tx: 0x01,
203 read_power: 0x0BB8,
204 write_power: 0x0BB8,
205 }])
206 );
207 }
208
209 #[derive(Debug)]
210 struct TestTransport {
211 rx: VecDeque<u8>,
212 tx: Vec<u8>,
213 }
214
215 impl TestTransport {
216 fn from_frames(frames: Vec<Vec<u8>>) -> Self {
217 let mut rx = VecDeque::new();
218 for frame in frames {
219 rx.extend(frame);
220 }
221 Self { rx, tx: Vec::new() }
222 }
223 }
224
225 impl ReaderTransport for TestTransport {
226 type Error = &'static str;
227
228 async fn write_all(&mut self, data: &[u8]) -> Result<(), Self::Error> {
229 self.tx.extend_from_slice(data);
230 Ok(())
231 }
232
233 async fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
234 if self.rx.len() < buf.len() {
235 return Err("eof");
236 }
237 for b in buf.iter_mut() {
238 *b = self.rx.pop_front().ok_or("eof")?;
239 }
240 Ok(())
241 }
242 }
243
244 fn frame(command: u8, status: u16, data: &[u8]) -> Vec<u8> {
245 let mut out = Vec::new();
246 out.push(0xFF);
247 out.push(data.len() as u8);
248 out.push(command);
249 out.extend_from_slice(&status.to_be_bytes());
250 out.extend_from_slice(data);
251 let crc = protocol_crc16(&out);
252 out.extend_from_slice(&crc.to_be_bytes());
253 out
254 }
255
256 #[test]
257 fn silion_reader_get_version_and_region() {
258 let version = frame(
259 CommandCode::GetVersion as u8,
260 0x0000,
261 &[
262 0x13, 0x04, 0x15, 0x00, 0xA8, 0x00, 0x00, 0x01, 0x20, 0x13, 0x05, 0x22, 0x13, 0x05,
263 0x23, 0x00, 0x00, 0x00, 0x00, 0x10,
264 ],
265 );
266 let region = frame(CommandCode::GetCurrentRegion as u8, 0x0000, &[0x01]);
267
268 let transport = TestTransport::from_frames(vec![version, region]);
269 let mut reader = SilionReader::new(transport);
270
271 let v = futures::executor::block_on(reader.get_version()).unwrap();
272 assert_eq!(v.supported_protocol, [0, 0, 0, 0x10]);
273 let r = futures::executor::block_on(reader.get_current_region()).unwrap();
274 assert_eq!(r, RegionCode::NorthAmerica);
275 }
276}