Compare commits

...

8 Commits

Author SHA1 Message Date
Leonora Tindall 36bdf9dd16
Move examples to examples/ 2019-12-31 14:51:30 -08:00
Leonora Tindall acf059aa1a
Cargo fmt 2019-12-31 14:50:46 -08:00
Leonora Tindall d42b0ec6b3
Add MAC pause/unpause
Also adds associated error variants `CannotPause` and `BadResponse`, an
associated helper function `Error::bad_response`, and the internal
helper function `transact_expecting` which shortens function bodies for
functions implementing simple handshake-like commands, like `mac
resume`.
2019-12-31 14:48:51 -08:00
Leonora Tindall fa31aab5cf
Add module reset and fix factory reset return
Previously, it returned a String but was documented to return bytes; it
now returns bytes.
2019-12-31 14:47:14 -08:00
Leonora Tindall 12cce7213b
Add factory reset functionality 2019-12-29 12:59:16 -08:00
Leonora Tindall 6318251226
Add `::system_version_bytes()` for uniformity
Other functions will return bytes, so it's weird to only be able to get
the version as a string.
2019-12-29 12:56:44 -08:00
Leonora Tindall dd2102d973
Formalize release process 2019-12-29 12:47:18 -08:00
Leonora Tindall 84fa9e5e01
Cut 0.1 release 2019-12-29 12:44:39 -08:00
6 changed files with 188 additions and 29 deletions

View File

@ -5,8 +5,28 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## Unreleased - v0.2.0
### Added
- `Rn2903::system_version_bytes()`
- `Rn2903::system_factory_reset()`
- `Rn2903::system_module_reset()`
- `Rn2903::mac_pause()` and `::mac_resume()`
- `BadResponse` and `CannotPause` error variants
- Examples directory with LED blinky and LoRa packet RX examples
### Changed
### Deprecated
### Removed
- `main.rs` (moved to an example)
### Fixed
### Security
## v0.1.0
### Added
- GNU GPL v3 license

View File

@ -17,3 +17,13 @@ CHANGELOG.md.
All method documentation is written in the present tense. For example, "Creates a new..."
rather than "Create a new...".
## Cutting a Release
When cutting a release:
- In CHANGELOG, rename the Unreleased section and add a new Unreleased section above it
- Make a commit with only that change
- Tag that commit like "v1.0.0"
- Push main and the tag
- `cargo publish`

32
examples/blinky.rs Normal file
View File

@ -0,0 +1,32 @@
use rn2903::Rn2903;
use std::env::args;
use std::process::exit;
use std::thread;
use std::time::Duration;
fn main() {
let args: Vec<_> = args().collect();
if args.len() <= 1 {
eprintln!("rn2903_blinky <serial port>");
eprintln!("\tReset the module and toggle pin 0b10 on and off.");
eprintln!("\tThis corresponds to the blue user LED on the LoStik.");
exit(1);
}
let mut txvr = Rn2903::new_at(&args[1]).expect("Could not open device. Error");
println!(
"Successfully connected. Version: {}",
txvr.system_version()
.expect("Could not read from device. Error:")
);
txvr.system_module_reset().unwrap();
txvr.transact(b"mac pause").unwrap();
loop {
txvr.transact(b"sys set pindig GPIO10 1").unwrap();
thread::sleep(Duration::from_millis(1000));
txvr.transact(b"sys set pindig GPIO10 0").unwrap();
thread::sleep(Duration::from_millis(1000));
}
}

View File

@ -0,0 +1,32 @@
use rn2903::Rn2903;
use std::env::args;
use std::process::exit;
use std::thread;
use std::time::Duration;
fn main() {
let args: Vec<_> = args().collect();
if args.len() <= 1 {
eprintln!("rn2903_lora_packet_rx <serial port>");
eprintln!("\tRecieve LoRa packets and print their corresponding hex values.");
exit(1);
}
let mut txvr = Rn2903::new_at(&args[1]).expect("Could not open device. Error");
println!(
"Successfully connected. Version: {}",
txvr.system_version()
.expect("Could not read from device. Error:")
);
txvr.mac_pause().unwrap();
txvr.transact(b"sys set pindig GPIO10 0").unwrap();
txvr.transact(b"radio rx 0").unwrap();
loop {
println!("{:?}", txvr.read_line().unwrap());
txvr.transact(b"sys set pindig GPIO10 1").unwrap();
thread::sleep(Duration::from_millis(100));
txvr.transact(b"sys set pindig GPIO10 0").unwrap();
txvr.transact(b"radio rx 0").unwrap();
}
}

View File

@ -36,6 +36,19 @@ quick_error! {
display("Could not verify version string. Expected a RN2903 firmware revision, got '{}'",
version)
}
/// The device returned a response that doesn't make sense, given the command that
/// was issued.
BadResponse(expected: String, response: String) {
description("bad response from module")
display("Received a bad response from the module. Expected '{}', got '{}'.",
expected, response)
}
/// The device is operating in a mode which prevents MAC functionality from being
/// paused, but a pause was requested.
CannotPause {
description("the LoRaWAN MAC cannot be paused")
display("The LoRaWAN MAC cannot be paused right now, but a pause was requested.")
}
/// 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.
@ -47,6 +60,12 @@ quick_error! {
}
}
impl Error {
fn bad_response<S: Into<String>, T: Into<String>>(expected: S, response: T) -> Self {
Self::BadResponse(expected.into(), response.into())
}
}
/// Universal `Result` wrapper for the RN2903 interface.
pub type Result<T> = std::result::Result<T, Error>;
@ -71,7 +90,7 @@ use std::thread;
/// # Examples
///
/// Opening a serial port with slightly modified settings. In this case, the baud rate
/// has been reduced.
/// has been reduced.
///
/// ```no_run
/// let settings = serialport::SerialPortSettings {
@ -130,12 +149,10 @@ pub struct Rn2903 {
port: Box<dyn SerialPort>,
}
/// # Meta (type) Functions
/// # 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.
///
/// the structure itself.
/// ## 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
@ -146,7 +163,7 @@ impl Rn2903 {
/// [`serial_config`](fn.serial_config.html).
///
/// # Example
///
///
/// Connecting to a module accessible over the USB0 TTY.
/// ```no_run
/// # use rn2903::Rn2903;
@ -179,7 +196,7 @@ impl Rn2903 {
pub fn new_unchecked(port: Box<dyn SerialPort>) -> Self {
Self { port }
}
/// Acquires temporary direct access to the captured `SerialPort` trait object.
///
/// Use this access to, for example, reconfigure the connection on the fly,
@ -206,7 +223,7 @@ impl Rn2903 {
}
}
/// # Low-level Communications
/// # Low-level Communications
impl Rn2903 {
/// Writes the specified command to the module and returns a single line in response.
///
@ -220,6 +237,21 @@ impl Rn2903 {
self.read_line()
}
/// Convenience function for situations where only one response is expected according
/// to the module's documentation. Receiving another response means something wacky
/// is going on.
fn transact_expecting(&mut self, command: &[u8], expectation: &[u8]) -> Result<()> {
let bytes = self.transact(command)?;
if bytes != expectation {
Err(Error::bad_response(
bytes_to_string(expectation),
bytes_to_string(&bytes),
))
} else {
Ok(())
}
}
/// Writes the specified command to the module, adding a CRLF and flushing the buffer.
///
/// Using [`::transact()`](#method.transact) is preferred.
@ -228,7 +260,7 @@ impl Rn2903 {
let mut cursor = 0;
while cursor < bytes.len() {
cursor += self.port.write(&bytes[cursor..])?;
}
}
self.port.flush()?;
thread::sleep(Duration::from_millis(500));
Ok(())
@ -274,7 +306,6 @@ impl Rn2903 {
Ok(vec)
}
}
/// # System API Functions
@ -285,5 +316,56 @@ impl Rn2903 {
pub fn system_version(&mut self) -> Result<String> {
let bytes = self.transact(b"sys get ver")?;
Ok(bytes_to_string(&bytes))
}
}
/// Queries the module for its firmware version information.
///
/// As `::system_version()`, but returns bytes.
pub fn system_version_bytes(&mut self) -> Result<Vec<u8>> {
self.transact(b"sys get ver")
}
/// Resets the CPU on the connected module. State in memory is lost and the MAC
/// starts up upon reboot, automatically loading default LoRaWAN settings.
///
/// Returns the system version, like `::system_version_bytes()`.
pub fn system_module_reset(&mut self) -> Result<Vec<u8>> {
self.transact(b"sys reset")
}
/// Performs a factory reset on the connected module. All EEPROM values are
/// restored to factory defaults. All LoRaWAN settings set by the user are lost.
///
/// Returns the system version, like `::system_version_bytes()`.
pub fn system_factory_reset(&mut self) -> Result<Vec<u8>> {
self.transact(b"sys factoryRESET")
}
}
/// # MAC API Functions
impl Rn2903 {
/// Pauses the LoRaWAN MAC functionality on the device, returning the number of
/// milliseconds for which the MAC can remain paused without affecting LoRaWAN
/// functionality.
///
/// This command can fail with `CannotPause`, meaning the device is operating in a
/// mode (like LoRaWAN Class C mode) in which pausing the MAC for any period of time
/// would result in degraded service.
pub fn mac_pause(&mut self) -> Result<u32> {
let val = bytes_to_string(&self.transact(b"mac pause")?);
let ms: u32 = match val.parse() {
Ok(v) => v,
Err(_) => return Err(Error::bad_response("<integer>", val)),
};
if ms == 0 {
Err(Error::CannotPause)
} else {
Ok(ms)
}
}
/// Resumes LoRaWAN MAC functionality on the device after being paused.
pub fn mac_resume(&mut self) -> Result<()> {
self.transact_expecting(b"mac resume", b"ok")
}
}

View File

@ -1,17 +0,0 @@
use rn2903::{Rn2903, bytes_to_string};
use std::thread;
use std::time::Duration;
fn main() {
let mut txvr = Rn2903::new_at("/dev/ttyUSB0").expect("Could not open device. Error");
println!(
"Successfully connected. Version: {}",
txvr
.system_version()
.expect("Could not read from device. Error:")
);
dbg!(bytes_to_string(&txvr.transact(b"sys set pindig GPIO10 1").unwrap()));
thread::sleep(Duration::from_millis(200));
dbg!(bytes_to_string(&txvr.transact(b"sys set pindig GPIO10 0").unwrap()));
}