A Rusty interface for the RN2903 LoRa module's serial protocol
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

526 lines
19KB

  1. //! ## A Rusty interface for the RN2903 serial protocol
  2. //!
  3. //! The RN2903 is a LoRa and FSK transciever for the 915MHz ISM band, commonly used in USB
  4. //! devices like the LoStik.
  5. //!
  6. //! This crate provides a safe, idiomatic interface using cross-platform native serial
  7. //! functionality via `serialport`. This supports, for instance, a LoStik connected to a USB
  8. //! TTY or virtual COM port, or a RN2903 connected via a TTL serial interface.
  9. //!
  10. //! See the [`Rn2903` struct](struct.Rn2903.html) for the bulk of the crate's functionality.
  11. //!
  12. //! # Examples
  13. //!
  14. //! Receiving and printing valid LoRa payloads.
  15. //!
  16. //! ```no_run
  17. //! # use rn2903::{Rn2903, ModulationMode};
  18. //! let mut txvr = Rn2903::new_at("/dev/ttyUSB0")
  19. //! .expect("Could not open device. Error");
  20. //! txvr.mac_pause().unwrap();
  21. //! txvr.radio_set_modulation_mode(ModulationMode::LoRa).unwrap();
  22. //! loop {
  23. //! if let Some(packet) = txvr.radio_rx(65535).unwrap() {
  24. //! println!("{:?}", packet);
  25. //! }
  26. //! }
  27. //! ```
  28. // One of the critical aspects of this library is error handling. Because it is intended
  29. // to communicate with an external device, any operation could discover a disconnection
  30. // from the RN2903 serial link, so everything which does such communication will return
  31. // a `Result<T, rn2903::Error>`.
  32. #[macro_use]
  33. extern crate quick_error;
  34. use hex;
  35. use std::io;
  36. quick_error! {
  37. /// The primary error type used for fallible operations on the RN2903.
  38. #[derive(Debug)]
  39. pub enum Error {
  40. /// The connection to the RN2903 was impossible for some reason. Perhaps an
  41. /// invalid port was specified, or this program does not have permission to
  42. /// access the specified port.
  43. ConnectionFailed(err: serialport::Error) {
  44. cause(err)
  45. description(err.description())
  46. from()
  47. }
  48. /// The device to which the serial link is connected does not appear to be
  49. /// a RN2903, because it did not respond to `sys get ver` correctly.
  50. WrongDevice(version: String) {
  51. description("failed to verify connected module")
  52. display("Could not verify version string. Expected a RN2903 firmware revision, got '{}'",
  53. version)
  54. }
  55. /// The device returned a response that doesn't make sense, given the command that
  56. /// was issued.
  57. BadResponse(expected: String, response: String) {
  58. description("bad response from module")
  59. display("Received a bad response from the module. Expected '{}', got '{}'.",
  60. expected, response)
  61. }
  62. /// The device is operating in a mode which prevents MAC functionality from being
  63. /// paused, but a pause was requested.
  64. CannotPause {
  65. description("the LoRaWAN MAC cannot be paused")
  66. display("The LoRaWAN MAC cannot be paused right now, but a pause was requested.")
  67. }
  68. /// The transceiver is busy with another operation, or is under the control of
  69. /// the MAC, and cannot be used to perform the requested operation.
  70. TransceiverBusy {
  71. description("the radio transceiver hardware is in use")
  72. display("The LoRa/FSK radio transceiver hardware is in use by another operation or the MAC layer and cannot be used to perform the requested operation.")
  73. }
  74. /// The program has become disconnected from the RN2903 module due to an I/O
  75. /// error. It is possible the device was physically disconnected, or that the
  76. /// host operating system closed the serial port for some reason.
  77. Disconnected(err: io::Error) {
  78. cause(err)
  79. description(err.description())
  80. from()
  81. }
  82. TransmissionUnsuccessful {
  83. description("the radio transmission was unsuccessful")
  84. display("if transmission was unsuccessful (interrupted by radio Watchdog Timer time-out)")
  85. }
  86. InvalidParam {
  87. description("the parameter is not valid")
  88. display("the parameter is not valid")
  89. }
  90. }
  91. }
  92. impl Error {
  93. fn bad_response<S: Into<String>, T: Into<String>>(expected: S, response: T) -> Self {
  94. Self::BadResponse(expected.into(), response.into())
  95. }
  96. }
  97. /// Universal `Result` wrapper for the RN2903 interface.
  98. pub type Result<T> = std::result::Result<T, Error>;
  99. // It's first necessary to actually connect to the module. To this end, the library
  100. // exports all the configuration information needed to configure a serial port to
  101. // communicate correctly with an RN2903.
  102. use core::convert::AsRef;
  103. use core::time::Duration;
  104. use serialport::prelude::*;
  105. use std::ffi::OsStr;
  106. use std::io::prelude::*;
  107. use std::thread;
  108. /// Returns the `SerialPortSettings` corresponding to the default settings of
  109. /// an RNB2903.
  110. ///
  111. /// Information obtained from Microchip document 40001811 revision B. Timeout is by
  112. /// default set to a very long time; this is sometimes modified on the `SerialPort` itself
  113. /// during certain operations.
  114. ///
  115. /// # Examples
  116. ///
  117. /// Opening a serial port with slightly modified settings. In this case, the baud rate
  118. /// has been reduced.
  119. ///
  120. /// ```no_run
  121. /// let settings = serialport::SerialPortSettings {
  122. /// baud_rate: 9600,
  123. /// ..rn2903::serial_config()
  124. /// };
  125. ///
  126. /// serialport::open_with_settings("/dev/ttyUSB0", &settings)
  127. /// .expect("Could not open serial port. Error");
  128. /// ```
  129. pub fn serial_config() -> SerialPortSettings {
  130. SerialPortSettings {
  131. baud_rate: 57600,
  132. data_bits: DataBits::Eight,
  133. flow_control: FlowControl::None,
  134. parity: Parity::None,
  135. stop_bits: StopBits::One,
  136. timeout: Duration::new(65535, 0),
  137. }
  138. }
  139. // Once connected to a serial port, the library needs to verify that it is actually
  140. // connected to a RN2903 and not some other serial device. To this end, the `Rn2903`
  141. // wrapper struct's `::new()` function checks the output of the `sys get ver` command,
  142. // which is well-specified.
  143. /// Turn the raw bytes into a String for display.
  144. pub fn bytes_to_string(bytes: &[u8]) -> String {
  145. (&*String::from_utf8_lossy(bytes)).into()
  146. }
  147. /// A handle to a serial link connected to a RN2903 module.
  148. ///
  149. /// This library guarantees safety regardless of the state of the RN2903. Refer to the
  150. /// documentation for sections and individual associated functions for specifics.
  151. ///
  152. /// # Examples
  153. ///
  154. /// Basic functionality can be obtained just by using `::new_at()` and `::transact()`.
  155. /// For instance, blinking the LoStik's LED:
  156. ///
  157. /// ```no_run
  158. /// # use rn2903::Rn2903;
  159. /// # use std::time::Duration;
  160. /// # use std::thread;
  161. /// let mut txvr = Rn2903::new_at("/dev/ttyUSB0")
  162. /// .expect("Could not open device. Error");
  163. /// loop {
  164. /// txvr.transact(b"radio set pindig GPIO10 0").unwrap();
  165. /// thread::sleep(Duration::from_millis(1000));
  166. /// txvr.transact(b"radio set pindig GPIO10 1").unwrap();
  167. /// thread::sleep(Duration::from_millis(1000));
  168. /// }
  169. /// ```
  170. pub struct Rn2903 {
  171. port: Box<dyn SerialPort>,
  172. }
  173. /// # Meta (type) Functions
  174. ///
  175. /// These functions deal with the type `Rn2903`, providing ways to create and manipulate
  176. /// the structure itself.
  177. /// ## Creating an `Rn2903`
  178. /// There are several ways to create a `Rn2903` wrapper for an RN2903 serial connection.
  179. /// `::new_at()` is the recommended method, but `::new()` can be useful if the platform
  180. /// does not support named serial ports, or some extra configuration is needed.
  181. impl Rn2903 {
  182. /// Opens a new connection to a module at the given path or port name, with the
  183. /// default (and usually correct) settings from
  184. /// [`serial_config`](fn.serial_config.html).
  185. ///
  186. /// # Example
  187. ///
  188. /// Connecting to a module accessible over the USB0 TTY.
  189. /// ```no_run
  190. /// # use rn2903::Rn2903;
  191. /// let txvr = Rn2903::new_at("/dev/ttyUSB0")
  192. /// .expect("Could not open device. Error");
  193. /// ```
  194. pub fn new_at<S: AsRef<OsStr>>(port_name: S) -> Result<Self> {
  195. let sp = serialport::open_with_settings(&port_name, &serial_config())?;
  196. Self::new(sp)
  197. }
  198. /// Open a new connection to a module over the connection described by the given
  199. /// `SerialPort` trait object.
  200. pub fn new(port: Box<dyn SerialPort>) -> Result<Self> {
  201. let mut new = Self::new_unchecked(port);
  202. let version = new.system_version()?;
  203. // RN2483 and RN2903 are the same, just EU and US chip, but talk the same
  204. if &version[0..6] != "RN2903" && &version[0..6] != "RN2483" {
  205. Err(Error::WrongDevice(version))
  206. } else {
  207. Ok(new)
  208. }
  209. }
  210. /// Open a new connection to a module over the connection described by the given
  211. /// `SerialPort` trait object without performing a `sys get ver` check.
  212. ///
  213. /// The results of operations on a `Rn2903` struct that does _not_ represent an
  214. /// actual connection to an RN2903 module are completely unpredictable, and may
  215. /// result in lots of badness (though not memory unsafety).
  216. pub fn new_unchecked(port: Box<dyn SerialPort>) -> Self {
  217. Self { port }
  218. }
  219. /// Acquires temporary direct access to the captured `SerialPort` trait object.
  220. ///
  221. /// Use this access to, for example, reconfigure the connection on the fly,
  222. /// or set flags that will be used by devices this crate is unaware of.
  223. ///
  224. /// # Example
  225. ///
  226. /// Raising and then lowering the RTS signal, for example to signal a bus observer
  227. /// to switch on.
  228. /// ```no_run
  229. /// # use rn2903::Rn2903;
  230. /// # use std::thread;
  231. /// # use std::time::Duration;
  232. /// # let mut txvr = Rn2903::new_at("/dev/ttyUSB0")
  233. /// # .expect("Could not open device. Error");
  234. /// txvr.port().write_request_to_send(true)
  235. /// .expect("Could not set RTS. Error");
  236. /// thread::sleep(Duration::from_millis(25));
  237. /// txvr.port().write_request_to_send(false)
  238. /// .expect("Could not set RTS. Error");
  239. /// ```
  240. pub fn port(&mut self) -> &mut dyn SerialPort {
  241. &mut *self.port
  242. }
  243. }
  244. /// # Low-level Communications
  245. impl Rn2903 {
  246. /// Writes the specified command to the module and returns a single line in response.
  247. ///
  248. /// This function adds the CRLF to the given command and returns the response without
  249. /// the CRLF.
  250. ///
  251. /// This is the preferred low-level communication method, since the RN2903 is supposed
  252. /// to respond with a single line to every command.
  253. pub fn transact(&mut self, command: &[u8]) -> Result<Vec<u8>> {
  254. self.send_line(command)?;
  255. self.read_line()
  256. }
  257. /// Convenience function for situations where only one response is expected according
  258. /// to the module's documentation. Receiving another response means something wacky
  259. /// is going on.
  260. fn transact_expecting(&mut self, command: &[u8], expectation: &[u8]) -> Result<()> {
  261. let bytes = self.transact(command)?;
  262. if bytes != expectation {
  263. Err(Error::bad_response(
  264. bytes_to_string(expectation),
  265. bytes_to_string(&bytes),
  266. ))
  267. } else {
  268. Ok(())
  269. }
  270. }
  271. /// Writes the specified command to the module, adding a CRLF and flushing the buffer.
  272. ///
  273. /// Using [`::transact()`](#method.transact) is preferred.
  274. pub fn send_line(&mut self, line: &[u8]) -> Result<()> {
  275. let bytes: Vec<u8> = line.iter().chain(b"\x0D\x0A".iter()).cloned().collect();
  276. let mut cursor = 0;
  277. while cursor < bytes.len() {
  278. cursor += self.port.write(&bytes[cursor..])?;
  279. }
  280. self.port.flush()?;
  281. thread::sleep(Duration::from_millis(500));
  282. Ok(())
  283. }
  284. /// Reads bytes from the device until a CRLF is encountered, then returns the bytes
  285. /// read, not including the CRLF.
  286. ///
  287. /// Using [`::transact()`](#method.transact) is preferred.
  288. // This operation waits 12ms between each 32-byte read because the LoStick has
  289. // the hiccups.
  290. pub fn read_line(&mut self) -> Result<Vec<u8>> {
  291. let mut vec = Vec::with_capacity(32);
  292. loop {
  293. let mut buf = [0; 32];
  294. self.port.read(&mut buf)?;
  295. vec.extend_from_slice(&buf);
  296. // Check if crlf was added to the buffer.
  297. let mut found_lf = false;
  298. let mut found_crlf = false;
  299. for byte in vec.iter().rev() {
  300. if found_lf {
  301. if *byte == b'\x0D' {
  302. found_crlf = true;
  303. break;
  304. }
  305. } else {
  306. found_lf = *byte == b'\x0A';
  307. }
  308. }
  309. if found_crlf {
  310. break;
  311. } else {
  312. thread::sleep(Duration::from_millis(12));
  313. }
  314. }
  315. // Remove zeroes and crlf
  316. while (b"\x00\x0D\x0A").contains(&vec[vec.len() - 1]) {
  317. vec.pop();
  318. }
  319. Ok(vec)
  320. }
  321. }
  322. /// An address in user-accessible nonvolatile memory. Guaranteed to be between 0x300 and
  323. /// 0x3FF.
  324. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
  325. pub struct NvmAddress(u16);
  326. impl NvmAddress {
  327. /// Create a new `NvmAddress` from a `u16`. The given value must be between 0x300 and
  328. /// 0x3FF.
  329. ///
  330. /// # Panics
  331. /// Panics if the given value is not between 0x300 and 0x3FF.
  332. pub fn new(value: u16) -> NvmAddress {
  333. if value < 0x300 {
  334. panic!("Attempted to construct NvmAddress less than 0x300.");
  335. }
  336. if value > 0x3FF {
  337. panic!("Attempted to construct NvmAddress more than 0x3FF.");
  338. }
  339. NvmAddress(value)
  340. }
  341. /// Return the address to which this NvmAddress refers. Guaranteed to be between
  342. /// 0x300 and 0x3FF.
  343. pub fn inner(self) -> u16 {
  344. self.0
  345. }
  346. }
  347. /// # System API Functions
  348. impl Rn2903 {
  349. /// Queries the module for its firmware version information.
  350. ///
  351. /// Returns a `String` like `RN2903 1.0.3 Aug 8 2017 15:11:09`
  352. pub fn system_version(&mut self) -> Result<String> {
  353. let bytes = self.transact(b"sys get ver")?;
  354. Ok(bytes_to_string(&bytes))
  355. }
  356. /// Queries the module for its firmware version information.
  357. ///
  358. /// As `::system_version()`, but returns bytes.
  359. pub fn system_version_bytes(&mut self) -> Result<Vec<u8>> {
  360. self.transact(b"sys get ver")
  361. }
  362. /// Resets the CPU on the connected module. State in memory is lost and the MAC
  363. /// starts up upon reboot, automatically loading default LoRaWAN settings.
  364. ///
  365. /// Returns the system version, like `::system_version_bytes()`.
  366. pub fn system_module_reset(&mut self) -> Result<Vec<u8>> {
  367. self.transact(b"sys reset")
  368. }
  369. /// Performs a factory reset on the connected module. All EEPROM values are
  370. /// restored to factory defaults. All LoRaWAN settings set by the user are lost.
  371. ///
  372. /// Returns the system version, like `::system_version_bytes()`.
  373. pub fn system_factory_reset(&mut self) -> Result<Vec<u8>> {
  374. self.transact(b"sys factoryRESET")
  375. }
  376. /// Set the value of the on-MCU nonvolatile memory at the given address to the given
  377. /// value.
  378. pub fn system_set_nvm(&mut self, address: NvmAddress, value: u8) -> Result<()> {
  379. self.transact_expecting(
  380. &format!("sys set nvm {:x} {:x}", address.inner(), value).into_bytes(),
  381. b"ok",
  382. )
  383. }
  384. /// Get the value of the on-MCU nonvolatile memory at the given address.
  385. pub fn system_get_nvm(&mut self, address: NvmAddress) -> Result<u8> {
  386. let response = bytes_to_string(
  387. &self.transact(&format!("sys get nvm {:x}", address.inner()).into_bytes())?,
  388. );
  389. match u8::from_str_radix(&response, 16) {
  390. Ok(v) => Ok(v),
  391. Err(_) => Err(Error::bad_response("<integer>", response)),
  392. }
  393. }
  394. }
  395. /// Types of modulation available for transmitting and receiving packets.
  396. #[derive(Debug, PartialEq, Eq)]
  397. pub enum ModulationMode {
  398. /// Regular digital frequency shift keying mode
  399. Fsk,
  400. /// LoRa chirp spread spectrum mode
  401. LoRa, // TODO: GFSK with radio set bt <value>
  402. }
  403. /// # Radio API Functions
  404. impl Rn2903 {
  405. /// Set the modulation mode used by the radio for transmission and reception.
  406. pub fn radio_set_modulation_mode(&mut self, mode: ModulationMode) -> Result<()> {
  407. match mode {
  408. ModulationMode::Fsk => self.transact_expecting(b"radio set mod fsk", b"ok"),
  409. ModulationMode::LoRa => self.transact_expecting(b"radio set mod lora", b"ok"),
  410. }
  411. }
  412. /// Open the receiver for the given timeout in symbols (for LoRa) or milliseconds
  413. /// (for FSK), returning `Ok(Some(_))` if a valid packet is received or `Ok(None)` if
  414. /// no packet is received before the timeout.
  415. pub fn radio_rx(&mut self, timeout: u16) -> Result<Option<Vec<u8>>> {
  416. let result = self.transact(&format!("radio rx {}", timeout).into_bytes())?;
  417. match &result[..] {
  418. b"ok" => (),
  419. b"busy" => return Err(Error::TransceiverBusy),
  420. v => return Err(Error::bad_response("ok | busy", bytes_to_string(v))),
  421. };
  422. let response = self.read_line()?;
  423. match &response[0..9] {
  424. b"radio_err" => Ok(None),
  425. b"radio_rx " => {
  426. let response_bytes: std::result::Result<Vec<u8>, _> = response[10..]
  427. .chunks(2)
  428. .map(bytes_to_string)
  429. .map(|b| u8::from_str_radix(&b, 16))
  430. .collect();
  431. match response_bytes {
  432. Ok(v) => Ok(Some(v)),
  433. Err(_) => Err(Error::bad_response(
  434. "radio_rx <bytes>",
  435. bytes_to_string(&response),
  436. )),
  437. }
  438. }
  439. _ => Err(Error::bad_response(
  440. "radio_err | radio_rx <bytes>",
  441. bytes_to_string(&response),
  442. )),
  443. }
  444. }
  445. pub fn radio_tx(&mut self, send: String) -> Result<Option<Vec<u8>>> {
  446. let result = self.transact(&format!("radio tx {}", hex::encode(send)).into_bytes())?;
  447. match &result[..] {
  448. b"ok" => {
  449. let sresult = self.read_line()?;
  450. match &sresult[..] {
  451. b"radio_tx_ok" => return Ok(None),
  452. b"radio_err" => return Err(Error::TransmissionUnsuccessful),
  453. v => return Err(Error::bad_response("nok", bytes_to_string(v))),
  454. }
  455. }
  456. b"invalid_param" => return Err(Error::InvalidParam),
  457. b"busy" => return Err(Error::TransceiverBusy),
  458. v => return Err(Error::bad_response("ok | busy", bytes_to_string(v))),
  459. };
  460. }
  461. }
  462. /// # MAC API Functions
  463. impl Rn2903 {
  464. /// Pauses the LoRaWAN MAC functionality on the device, returning the number of
  465. /// milliseconds for which the MAC can remain paused without affecting LoRaWAN
  466. /// functionality.
  467. ///
  468. /// This command can fail with `CannotPause`, meaning the device is operating in a
  469. /// mode (like LoRaWAN Class C mode) in which pausing the MAC for any period of time
  470. /// would result in degraded service.
  471. pub fn mac_pause(&mut self) -> Result<u32> {
  472. let val = bytes_to_string(&self.transact(b"mac pause")?);
  473. let ms: u32 = match val.parse() {
  474. Ok(v) => v,
  475. Err(_) => return Err(Error::bad_response("<integer>", val)),
  476. };
  477. if ms == 0 {
  478. Err(Error::CannotPause)
  479. } else {
  480. Ok(ms)
  481. }
  482. }
  483. /// Resumes LoRaWAN MAC functionality on the device after being paused.
  484. pub fn mac_resume(&mut self) -> Result<()> {
  485. self.transact_expecting(b"mac resume", b"ok")
  486. }
  487. }