Better docs and rename transact()
This commit is contained in:
parent
f4bd7b67e4
commit
8e52f10ffc
|
@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
- GNU GPL v3 license
|
- GNU GPL v3 license
|
||||||
- Cargo metadata
|
- Cargo metadata
|
||||||
|
- Basic functionality
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
175
src/lib.rs
175
src/lib.rs
|
@ -1,3 +1,14 @@
|
||||||
|
//! ## A Rusty interface for the RN2903 serial protocol
|
||||||
|
//!
|
||||||
|
//! The RN2903 is a LoRa and FSK transciever for the 915MHz ISM band, commonly used in USB
|
||||||
|
//! devices like the LoStik.
|
||||||
|
//!
|
||||||
|
//! This crate provides a safe, idiomatic interface using cross-platform native serial
|
||||||
|
//! functionality via `serialport`. This supports, for instance, a LoStik connected to a USB
|
||||||
|
//! TTY or virtual COM port, or a RN2903 connected via a TTL serial interface.
|
||||||
|
//!
|
||||||
|
//! See the [`Rn2903` struct](struct.Rn2903.html) for the bulk of the crate's functionality.
|
||||||
|
|
||||||
// One of the critical aspects of this library is error handling. Because it is intended
|
// 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
|
// 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
|
// from the RN2903 serial link, so everything which does such communication will return
|
||||||
|
@ -7,6 +18,7 @@ extern crate quick_error;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
quick_error! {
|
quick_error! {
|
||||||
|
/// The primary error type used for fallible operations on the RN2903.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// The connection to the RN2903 was impossible for some reason. Perhaps an
|
/// The connection to the RN2903 was impossible for some reason. Perhaps an
|
||||||
|
@ -36,7 +48,7 @@ quick_error! {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Universal `Result` wrapper for the RN2903 interface.
|
/// Universal `Result` wrapper for the RN2903 interface.
|
||||||
type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
// It's first necessary to actually connect to the module. To this end, the library
|
// 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
|
// exports all the configuration information needed to configure a serial port to
|
||||||
|
@ -49,12 +61,27 @@ use std::ffi::OsStr;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
/// Returns a `serialport::SerialPortSettings` corresponding to the default settings of
|
/// Returns the `SerialPortSettings` corresponding to the default settings of
|
||||||
/// an RNB2903. Use this to configure your serial port.
|
/// an RNB2903.
|
||||||
///
|
///
|
||||||
/// Information obtained from Microchip document 40001811 revision B. Timeout is by
|
/// 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
|
/// default set to a very long time; this is sometimes modified on the `SerialPort` itself
|
||||||
/// during certain operations.
|
/// during certain operations.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Opening a serial port with slightly modified settings. In this case, the baud rate
|
||||||
|
/// has been reduced.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// let settings = serialport::SerialPortSettings {
|
||||||
|
/// baud_rate: 9600,
|
||||||
|
/// ..rn2903::serial_config()
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// serialport::open_with_settings("/dev/ttyUSB0", &settings)
|
||||||
|
/// .expect("Could not open serial port. Error");
|
||||||
|
/// ```
|
||||||
pub fn serial_config() -> SerialPortSettings {
|
pub fn serial_config() -> SerialPortSettings {
|
||||||
SerialPortSettings {
|
SerialPortSettings {
|
||||||
baud_rate: 57600,
|
baud_rate: 57600,
|
||||||
|
@ -62,7 +89,7 @@ pub fn serial_config() -> SerialPortSettings {
|
||||||
flow_control: FlowControl::None,
|
flow_control: FlowControl::None,
|
||||||
parity: Parity::None,
|
parity: Parity::None,
|
||||||
stop_bits: StopBits::One,
|
stop_bits: StopBits::One,
|
||||||
timeout: Duration::new(1, 0),
|
timeout: Duration::new(65535, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,30 +98,70 @@ pub fn serial_config() -> SerialPortSettings {
|
||||||
// wrapper struct's `::new()` function checks the output of the `sys get ver` command,
|
// wrapper struct's `::new()` function checks the output of the `sys get ver` command,
|
||||||
// which is well-specified.
|
// which is well-specified.
|
||||||
|
|
||||||
// In order to turn the raw bytes into a String for display, this helper function comes
|
/// Turn the raw bytes into a String for display.
|
||||||
// in handy.
|
pub fn bytes_to_string(bytes: &[u8]) -> String {
|
||||||
fn bytes_to_string(bytes: &[u8]) -> String {
|
|
||||||
(&*String::from_utf8_lossy(bytes)).into()
|
(&*String::from_utf8_lossy(bytes)).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A handle to a serial link connected to a RN2903 module.
|
/// A handle to a serial link connected to a RN2903 module.
|
||||||
///
|
///
|
||||||
/// This library guarantees safety regardless of the state of the RN2903.
|
/// This library guarantees safety regardless of the state of the RN2903. Refer to the
|
||||||
|
/// documentation for sections and individual associated functions for specifics.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Basic functionality can be obtained just by using `::new_at()` and `::transact()`.
|
||||||
|
/// For instance, blinking the LoStik's LED:
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use rn2903::Rn2903;
|
||||||
|
/// # use std::time::Duration;
|
||||||
|
/// # use std::thread;
|
||||||
|
/// let mut txvr = Rn2903::new_at("/dev/ttyUSB0")
|
||||||
|
/// .expect("Could not open device. Error");
|
||||||
|
/// loop {
|
||||||
|
/// txvr.transact(b"radio set pindig GPIO10 0").unwrap();
|
||||||
|
/// thread::sleep(Duration::from_millis(1000));
|
||||||
|
/// txvr.transact(b"radio set pindig GPIO10 1").unwrap();
|
||||||
|
/// thread::sleep(Duration::from_millis(1000));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub struct Rn2903 {
|
pub struct Rn2903 {
|
||||||
port: Box<dyn SerialPort>,
|
port: Box<dyn SerialPort>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Meta (type) Functions
|
||||||
|
///
|
||||||
|
/// These functions deal with the type `Rn2903`, providing ways to create and manipulate
|
||||||
|
/// the structure itself. Aside from performing validation of the device on the other side
|
||||||
|
/// of the serial link, these functions do not communicate with the module.
|
||||||
|
///
|
||||||
|
/// ## Creating an `Rn2903`
|
||||||
|
/// There are several ways to create a `Rn2903` wrapper for an RN2903 serial connection.
|
||||||
|
/// `::new_at()` is the recommended method, but `::new()` can be useful if the platform
|
||||||
|
/// does not support named serial ports, or some extra configuration is needed.
|
||||||
impl Rn2903 {
|
impl Rn2903 {
|
||||||
/// Open a new connection to a module at the given path or port name, with the
|
/// Opens a new connection to a module at the given path or port name, with the
|
||||||
/// default settings.
|
/// default (and usually correct) settings from
|
||||||
|
/// [`serial_config`](fn.serial_config.html).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Connecting to a module accessible over the USB0 TTY.
|
||||||
|
/// ```no_run
|
||||||
|
/// # use rn2903::Rn2903;
|
||||||
|
/// let txvr = Rn2903::new_at("/dev/ttyUSB0")
|
||||||
|
/// .expect("Could not open device. Error");
|
||||||
|
/// ```
|
||||||
pub fn new_at<S: AsRef<OsStr>>(port_name: S) -> Result<Self> {
|
pub fn new_at<S: AsRef<OsStr>>(port_name: S) -> Result<Self> {
|
||||||
let sp = serialport::open_with_settings(&port_name, &serial_config())?;
|
let sp = serialport::open_with_settings(&port_name, &serial_config())?;
|
||||||
Self::new(sp)
|
Self::new(sp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open a new connection to a module over the given serial connection.
|
/// Open a new connection to a module over the connection described by the given
|
||||||
|
/// `SerialPort` trait object.
|
||||||
pub fn new(port: Box<dyn SerialPort>) -> Result<Self> {
|
pub fn new(port: Box<dyn SerialPort>) -> Result<Self> {
|
||||||
let mut new = Self { port };
|
let mut new = Self::new_unchecked(port);
|
||||||
let version = new.system_version()?;
|
let version = new.system_version()?;
|
||||||
if &version[0..6] != "RN2903" {
|
if &version[0..6] != "RN2903" {
|
||||||
Err(Error::WrongDevice(version))
|
Err(Error::WrongDevice(version))
|
||||||
|
@ -103,30 +170,78 @@ impl Rn2903 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query the module for its firmware version information.
|
/// Open a new connection to a module over the connection described by the given
|
||||||
|
/// `SerialPort` trait object without performing a `sys get ver` check.
|
||||||
///
|
///
|
||||||
/// Returns a `String` like `RN2903 1.0.3 Aug 8 2017 15:11:09`
|
/// The results of operations on a `Rn2903` struct that does _not_ represent an
|
||||||
pub fn system_version(&mut self) -> Result<String> {
|
/// actual connection to an RN2903 module are completely unpredictable, and may
|
||||||
let bytes = self.transact_command(b"sys get ver\x0D\x0A")?;
|
/// result in lots of badness (though not memory unsafety).
|
||||||
Ok(bytes_to_string(&bytes))
|
pub fn new_unchecked(port: Box<dyn SerialPort>) -> Self {
|
||||||
|
Self { port }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the specified command to the module and get a single line of response.
|
/// Acquires temporary direct access to the captured `SerialPort` trait object.
|
||||||
|
///
|
||||||
|
/// Use this access to, for example, reconfigure the connection on the fly,
|
||||||
|
/// or set flags that will be used by devices this crate is unaware of.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Raising and then lowering the RTS signal, for example to signal a bus observer
|
||||||
|
/// to switch on.
|
||||||
|
/// ```no_run
|
||||||
|
/// # use rn2903::Rn2903;
|
||||||
|
/// # use std::thread;
|
||||||
|
/// # use std::time::Duration;
|
||||||
|
/// # let mut txvr = Rn2903::new_at("/dev/ttyUSB0")
|
||||||
|
/// # .expect("Could not open device. Error");
|
||||||
|
/// txvr.port().write_request_to_send(true)
|
||||||
|
/// .expect("Could not set RTS. Error");
|
||||||
|
/// thread::sleep(Duration::from_millis(25));
|
||||||
|
/// txvr.port().write_request_to_send(false)
|
||||||
|
/// .expect("Could not set RTS. Error");
|
||||||
|
/// ```
|
||||||
|
pub fn port(&mut self) -> &mut dyn SerialPort {
|
||||||
|
&mut *self.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Low-level Communications
|
||||||
|
impl Rn2903 {
|
||||||
|
/// Writes the specified command to the module and returns a single line in response.
|
||||||
///
|
///
|
||||||
/// This function adds the CRLF to the given command and returns the response without
|
/// This function adds the CRLF to the given command and returns the response without
|
||||||
/// the CRLF.
|
/// the CRLF.
|
||||||
pub fn transact_command(&mut self, command: &[u8]) -> Result<Vec<u8>> {
|
///
|
||||||
use std::io::IoSlice;
|
/// This is the preferred low-level communication method, since the RN2903 is supposed
|
||||||
self.port.write_vectored(&[IoSlice::new(command), IoSlice::new(b"\x0D\x0A")])?;
|
/// to respond with a single line to every command.
|
||||||
self.port.flush()?;
|
pub fn transact(&mut self, command: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
self.send_line(command)?;
|
||||||
self.read_line()
|
self.read_line()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read bytes from the device until a CRLF is encountered, then returns the bytes
|
/// Writes the specified command to the module, adding a CRLF and flushing the buffer.
|
||||||
|
///
|
||||||
|
/// Using [`::transact()`](#method.transact) is preferred.
|
||||||
|
pub fn send_line(&mut self, line: &[u8]) -> Result<()> {
|
||||||
|
use std::io::IoSlice;
|
||||||
|
let bytes: Vec<u8> = line.iter().chain(b"\x0D\x0A".iter()).cloned().collect();
|
||||||
|
let mut cursor = 0;
|
||||||
|
while cursor < bytes.len() {
|
||||||
|
cursor += self.port.write(&bytes[cursor..])?;
|
||||||
|
}
|
||||||
|
self.port.flush()?;
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads bytes from the device until a CRLF is encountered, then returns the bytes
|
||||||
/// read, not including the CRLF.
|
/// read, not including the CRLF.
|
||||||
|
///
|
||||||
|
/// Using [`::transact()`](#method.transact) is preferred.
|
||||||
// This operation waits 12ms between each 32-byte read because the LoStick has
|
// This operation waits 12ms between each 32-byte read because the LoStick has
|
||||||
// the hiccups.
|
// the hiccups.
|
||||||
fn read_line(&mut self) -> Result<Vec<u8>> {
|
pub fn read_line(&mut self) -> Result<Vec<u8>> {
|
||||||
let mut vec = Vec::with_capacity(32);
|
let mut vec = Vec::with_capacity(32);
|
||||||
loop {
|
loop {
|
||||||
let mut buf = [0; 32];
|
let mut buf = [0; 32];
|
||||||
|
@ -160,4 +275,16 @@ impl Rn2903 {
|
||||||
|
|
||||||
Ok(vec)
|
Ok(vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # System API Functions
|
||||||
|
impl Rn2903 {
|
||||||
|
/// Queries 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<String> {
|
||||||
|
let bytes = self.transact(b"sys get ver")?;
|
||||||
|
Ok(bytes_to_string(&bytes))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue