// One of the critical aspects of this library is error handling. Because it is intended // to communicate with an external device, any operation could discover a disconnection // from the RN2903 serial link, so everything which does such communication will return // a `Result`. #[macro_use] extern crate quick_error; use std::io; quick_error! { #[derive(Debug)] pub enum Error { /// The connection to the RN2903 was impossible for some reason. Perhaps an /// invalid port was specified, or this program does not have permission to /// access the specified port. ConnectionFailed(err: serialport::Error) { cause(err) description(err.description()) from() } /// The device to which the serial link is connected does not appear to be /// a RN2903, because it did not respond to `sys get ver` correctly. WrongDevice(version: String) { description("failed to verify connected module") display("Could not verify version string. Expected a RN2903 firmware revision, got '{}'", version) } /// The program has become disconnected from the RN2903 module due to an I/O /// error. It is possible the device was physically disconnected, or that the /// host operating system closed the serial port for some reason. Disconnected(err: io::Error) { cause(err) description(err.description()) from() } } } /// Universal `Result` wrapper for the RN2903 interface. type Result = std::result::Result; // It's first necessary to actually connect to the module. To this end, the library // exports all the configuration information needed to configure a serial port to // communicate correctly with an RN2903. use core::convert::AsRef; use core::time::Duration; use serialport::prelude::*; use std::ffi::OsStr; use std::io::prelude::*; use std::thread; /// Returns a `serialport::SerialPortSettings` corresponding to the default settings of /// an RNB2903. Use this to configure your serial port. /// /// Information obtained from Microchip document 40001811 revision B. Timeout is by /// default set to a very long time; this is sometimes modified on the `SerialPort` itself /// during certain operations. pub fn serial_config() -> SerialPortSettings { SerialPortSettings { baud_rate: 57600, data_bits: DataBits::Eight, flow_control: FlowControl::None, parity: Parity::None, stop_bits: StopBits::One, timeout: Duration::new(1, 0), } } // Once connected to a serial port, the library needs to verify that it is actually // connected to a RN2903 and not some other serial device. To this end, the `Rn2903` // wrapper struct's `::new()` function checks the output of the `sys get ver` command, // which is well-specified. // In order to turn the raw bytes into a String for display, this helper function comes // in handy. fn bytes_to_string(bytes: &[u8]) -> String { (&*String::from_utf8_lossy(bytes)).into() } /// A handle to a serial link connected to a RN2903 module. /// /// This library guarantees safety regardless of the state of the RN2903. pub struct Rn2903 { port: Box, } impl Rn2903 { /// Open a new connection to a module at the given path or port name, with the /// default settings. pub fn new_at>(port_name: S) -> Result { let sp = serialport::open_with_settings(&port_name, &serial_config())?; Self::new(sp) } /// Open a new connection to a module over the given serial connection. pub fn new(port: Box) -> Result { let mut new = Self { port }; let version = new.system_version()?; if &version[0..6] != "RN2903" { Err(Error::WrongDevice(version)) } else { Ok(new) } } /// Query the module for its firmware version information. /// /// Returns a `String` like `RN2903 1.0.3 Aug 8 2017 15:11:09` pub fn system_version(&mut self) -> Result { let bytes = self.transact_command(b"sys get ver\x0D\x0A")?; Ok(bytes_to_string(&bytes)) } /// Write the specified command to the module and get a single line of response. /// /// This function adds the CRLF to the given command and returns the response without /// the CRLF. pub fn transact_command(&mut self, command: &[u8]) -> Result> { use std::io::IoSlice; self.port.write_vectored(&[IoSlice::new(command), IoSlice::new(b"\x0D\x0A")])?; self.port.flush()?; self.read_line() } /// Read bytes from the device until a CRLF is encountered, then returns the bytes /// read, not including the CRLF. // This operation waits 12ms between each 32-byte read because the LoStick has // the hiccups. fn read_line(&mut self) -> Result> { let mut vec = Vec::with_capacity(32); loop { let mut buf = [0; 32]; self.port.read(&mut buf)?; vec.extend_from_slice(&buf); // Check if crlf was added to the buffer. let mut found_lf = false; let mut found_crlf = false; for byte in vec.iter().rev() { if found_lf { if *byte == b'\x0D' { found_crlf = true; break; } } else { found_lf = *byte == b'\x0A'; } } if found_crlf { break; } else { thread::sleep(Duration::from_millis(12)); } } // Remove zeroes and crlf while (b"\x00\x0D\x0A").contains(&vec[vec.len() - 1]) { vec.pop(); } Ok(vec) } }