From a7e93ab56baa4c0d5fb1e2c4f2c56498570624a3 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Tue, 7 Apr 2026 15:45:34 -0600 Subject: [PATCH] =?UTF-8?q?feat!:=20v0.2.0=20=E2=80=94=20ping-on-connect?= =?UTF-8?q?=20validation,=20try=5Fconnect,=20publish=20polish?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ping-on-connect handshake: all connect functions validate the device within 200ms, rejecting non-DongLoRa serial devices immediately - Add try_connect() — non-blocking connect that errors instead of blocking forever when no USB device is present - Add payload size validation in transmit() (max 256 bytes) - Add ErrorCode: impl std::error::Error - Fix drain_rx error handling (no longer swallows original error) - Fix repository URL to github.com/donglora/client-rs - Remove wire-level tag constants from top-level re-exports - Add rust-version (MSRV 1.85), homepage, exclude fields to Cargo.toml - Add examples/ping.rs - Allow clippy unwrap/expect in test modules --- CHANGELOG.md | 30 ++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 7 +++- README.md | 40 +++++++++++++----- examples/ping.rs | 25 ++++++++++++ src/client.rs | 64 +++++++++++++++++------------ src/codec.rs | 36 ++++------------ src/connect.rs | 104 +++++++++++++++++++++++++++++++++++------------ src/lib.rs | 14 +++---- src/protocol.rs | 64 +++++++---------------------- src/transport.rs | 30 ++++---------- 11 files changed, 242 insertions(+), 174 deletions(-) create mode 100644 examples/ping.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index cef926a..e05efc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## 0.2.0 — 2026-04-07 + +### Features + +- **Ping-on-connect validation** — all connect functions automatically ping the + device after connecting and reject non-DongLoRa devices within 200ms. No more + accidentally talking LoRa protocol to an Arduino. +- **`try_connect(timeout)`** — non-blocking alternative to `connect()`. Returns + an error immediately if no DongLoRa device is found instead of blocking + indefinitely. +- **USB-UART bridge chip fallback** — `find_port()` now detects boards using + CP2102, CH9102, CH340, or FT232R bridge chips (validated via ping). +- **Payload size validation** — `transmit()` rejects payloads exceeding 256 + bytes with a clear error before hitting the wire. +- **`ErrorCode` implements `std::error::Error`** — usable in error chains. + +### Breaking changes + +- Wire-level tag constants (`CMD_TAG_*`, `RESP_TAG_*`, `ERROR_INVALID_CONFIG`) + removed from the top-level re-exports. Access them via + `donglora_client::protocol::*` if needed. +- `connect()` and friends now validate the device on connect. Code that + previously connected to non-DongLoRa serial devices will now get an error. + +### Fixes + +- Fixed repository URL in crate metadata. +- `drain_rx` no longer swallows the original error if timeout restore also fails. +- Removed dead `firmware/PROTOCOL.md` reference from docs. + ## 0.1.0 — 2026-04-06 Initial release. diff --git a/Cargo.lock b/Cargo.lock index 039bb41..f7f9e15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,7 +44,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "donglora-client" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "serialport", diff --git a/Cargo.toml b/Cargo.toml index ee6579c..16622f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,16 @@ [package] name = "donglora-client" -version = "0.1.0" +version = "0.2.0" edition = "2024" +rust-version = "1.85" authors = ["Stephen Waits "] license = "MIT" description = "DongLoRa host library — connect, configure, send/receive LoRa packets" -repository = "https://github.com/swaits/donglora" +repository = "https://github.com/donglora/client-rs" +homepage = "https://github.com/donglora/client-rs" keywords = ["lora", "radio", "usb", "serial"] categories = ["hardware-support"] +exclude = ["CLAUDE_TODO.md", "deny.toml", "justfile", "mise.toml", "rustfmt.toml", ".jj"] [dependencies] anyhow = "1" diff --git a/README.md b/README.md index d84defb..196e83f 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,21 @@ # DongLoRa Rust Client Rust client library for talking to a DongLoRa device — either directly -over USB or through the [mux daemon](../../mux/). +over USB or through a mux daemon. -## What's in Here +## Install -- `src/protocol.rs` — wire protocol types (`RadioConfig`, `Command`, `Response`, `ErrorCode`) -- `src/codec.rs` — COBS framing, frame accumulator -- `src/discovery.rs` — USB VID:PID device discovery -- `src/transport.rs` — serial and mux socket transports -- `src/client.rs` — high-level `Client` with send/recv -- `src/connect.rs` — auto-detection (mux socket, TCP, direct USB) +```toml +[dependencies] +donglora-client = "0.2" +``` -## Usage +## Quick Start ```rust use donglora_client::*; let mut client = connect_default()?; -client.ping()?; client.set_config(RadioConfig::default())?; client.start_rx()?; @@ -29,6 +26,29 @@ loop { } ``` +All `connect` functions automatically validate the device by pinging it, +so you'll get a clear error immediately if the port isn't a real DongLoRa. + +## What's in Here + +- `src/protocol.rs` — wire protocol types (`RadioConfig`, `Command`, `Response`, `ErrorCode`) +- `src/codec.rs` — COBS framing, frame accumulator +- `src/discovery.rs` — USB VID:PID device discovery +- `src/transport.rs` — serial and mux socket transports +- `src/client.rs` — high-level `Client` with send/recv +- `src/connect.rs` — auto-detection (mux socket, TCP, direct USB) + +## Connection Priority + +[`connect`] and [`try_connect`] try these in order: + +1. **TCP mux** — via `DONGLORA_MUX_TCP` env var +2. **Unix socket mux** — checks `$XDG_RUNTIME_DIR/donglora/mux.sock` or `/tmp/donglora-mux.sock` +3. **Direct USB serial** — auto-detects by VID:PID + +`try_connect` returns an error immediately if no device is found. +`connect` blocks until a USB device appears. + ## Dependencies - `ucobs` — COBS framing (same implementation as the firmware) diff --git a/examples/ping.rs b/examples/ping.rs new file mode 100644 index 0000000..b3bc687 --- /dev/null +++ b/examples/ping.rs @@ -0,0 +1,25 @@ +//! Connect to a DongLoRa device, ping it, and print the MAC address. +//! +//! Run with: cargo run --example ping + +use donglora_client::{RadioConfig, try_connect}; +use std::time::Duration; + +fn main() -> anyhow::Result<()> { + let mut client = try_connect(Duration::from_secs(2))?; + println!("connected!"); + + client.ping()?; + println!("ping ok"); + + let mac = client.get_mac()?; + println!("MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + let config = client.get_config()?; + println!("current config: {config:?}"); + + client.set_config(RadioConfig::default())?; + println!("config set to defaults"); + + Ok(()) +} diff --git a/src/client.rs b/src/client.rs index c9142b9..c23f225 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,9 +5,10 @@ //! while waiting for solicited responses). use std::collections::VecDeque; +use std::time::Duration; use crate::codec::{encode_frame, read_frame}; -use crate::protocol::{Command, ErrorCode, RadioConfig, Response}; +use crate::protocol::{Command, ErrorCode, MAX_PAYLOAD, RadioConfig, Response}; use crate::transport::Transport; /// Maximum number of buffered RxPackets before oldest are dropped. @@ -16,6 +17,9 @@ const RX_BUFFER_CAP: usize = 256; /// Maximum frames to read while waiting for a solicited response before giving up. const MAX_UNSOLICITED_BEFORE_TIMEOUT: usize = 50; +/// Timeout for the ping-on-connect handshake. +const HANDSHAKE_TIMEOUT: Duration = Duration::from_millis(200); + /// High-level DongLoRa client, generic over transport. /// /// Works with any [`Transport`]: direct USB serial, Unix socket mux, or TCP mux. @@ -27,10 +31,7 @@ pub struct Client { impl Client { /// Create a new client wrapping the given transport. pub fn new(transport: T) -> Self { - Self { - transport, - rx_buffer: VecDeque::with_capacity(64), - } + Self { transport, rx_buffer: VecDeque::with_capacity(64) } } /// Send a command and wait for the solicited response. @@ -43,13 +44,12 @@ impl Client { std::io::Write::flush(&mut self.transport)?; for _ in 0..MAX_UNSOLICITED_BEFORE_TIMEOUT { - let data = read_frame(&mut self.transport)? - .ok_or_else(|| anyhow::anyhow!("timeout waiting for response"))?; - let resp = Response::from_bytes(&data) - .ok_or_else(|| { - let hex: Vec = data.iter().map(|b| format!("{b:02x}")).collect(); - anyhow::anyhow!("malformed response ({} bytes): [{}]", data.len(), hex.join(" ")) - })?; + let data = + read_frame(&mut self.transport)?.ok_or_else(|| anyhow::anyhow!("timeout waiting for response"))?; + let resp = Response::from_bytes(&data).ok_or_else(|| { + let hex: Vec = data.iter().map(|b| format!("{b:02x}")).collect(); + anyhow::anyhow!("malformed response ({} bytes): [{}]", data.len(), hex.join(" ")) + })?; if resp.is_rx_packet() { self.buffer_rx(resp); continue; @@ -69,8 +69,8 @@ impl Client { let Some(data) = read_frame(&mut self.transport)? else { return Ok(None); }; - let resp = Response::from_bytes(&data) - .ok_or_else(|| anyhow::anyhow!("malformed response ({} bytes)", data.len()))?; + let resp = + Response::from_bytes(&data).ok_or_else(|| anyhow::anyhow!("malformed response ({} bytes)", data.len()))?; if resp.is_rx_packet() { Ok(Some(resp)) } else { @@ -87,8 +87,7 @@ impl Client { let mut packets: Vec = self.rx_buffer.drain(..).collect(); let old_timeout = self.transport.timeout(); - self.transport - .set_timeout(std::time::Duration::from_millis(10))?; + self.transport.set_timeout(std::time::Duration::from_millis(10))?; let result: anyhow::Result<()> = (|| { loop { @@ -103,12 +102,27 @@ impl Client { } })(); - // Always restore timeout, even if the loop errored - self.transport.set_timeout(old_timeout)?; + // Always restore timeout, even if the loop errored. + // Preserve the original error if both fail. + let restore_result = self.transport.set_timeout(old_timeout); result?; + restore_result?; Ok(packets) } + /// Validate the connection by pinging the device. + /// + /// Uses a short timeout to fail fast on non-DongLoRa devices (e.g. USB-UART + /// bridges that happen to match known VID:PIDs). Called automatically by the + /// [`connect`](crate::connect::connect) family of functions. + pub fn validate(&mut self) -> anyhow::Result<()> { + let saved = self.transport.timeout(); + self.transport.set_timeout(HANDSHAKE_TIMEOUT)?; + let result = self.ping(); + self.transport.set_timeout(saved)?; + result.map_err(|_| anyhow::anyhow!("device did not respond to ping — not a DongLoRa device")) + } + /// Send a Ping and verify the Pong response. pub fn ping(&mut self) -> anyhow::Result<()> { match self.send(Command::Ping)? { @@ -145,15 +159,11 @@ impl Client { } /// Transmit a LoRa packet. - pub fn transmit( - &mut self, - payload: &[u8], - config: Option, - ) -> anyhow::Result<()> { - let cmd = Command::Transmit { - config, - payload: payload.to_vec(), - }; + pub fn transmit(&mut self, payload: &[u8], config: Option) -> anyhow::Result<()> { + if payload.len() > MAX_PAYLOAD { + anyhow::bail!("payload too large ({} bytes, max {MAX_PAYLOAD})", payload.len()); + } + let cmd = Command::Transmit { config, payload: payload.to_vec() }; match self.send(cmd)? { Response::TxDone => Ok(()), Response::Error(ErrorCode::TxTimeout) => anyhow::bail!("transmit timed out"), diff --git a/src/codec.rs b/src/codec.rs index fcdcfde..c1331ac 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -47,9 +47,7 @@ pub struct FrameReader { impl FrameReader { /// Create a new empty frame reader. pub fn new() -> Self { - Self { - buf: Vec::with_capacity(512), - } + Self { buf: Vec::with_capacity(512) } } /// Feed raw bytes and return all complete decoded frames. @@ -107,12 +105,7 @@ pub fn read_frame(reader: &mut dyn std::io::Read) -> anyhow::Result - { + Err(e) if matches!(e.kind(), std::io::ErrorKind::TimedOut | std::io::ErrorKind::WouldBlock) => { return Ok(None); } Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue, @@ -124,14 +117,13 @@ pub fn read_frame(reader: &mut dyn std::io::Read) -> anyhow::Result Option { /// Connect to the mux daemon via Unix domain socket. #[cfg(unix)] -pub fn mux_connect( - path: Option<&str>, - timeout: Duration, -) -> anyhow::Result> { +pub fn mux_connect(path: Option<&str>, timeout: Duration) -> anyhow::Result> { let path = match path { Some(p) => p.to_string(), - None => find_mux_socket() - .ok_or_else(|| anyhow::anyhow!("no mux socket found"))?, + None => find_mux_socket().ok_or_else(|| anyhow::anyhow!("no mux socket found"))?, }; let transport = MuxTransport::unix(&path, timeout)?; - Ok(Client::new(transport)) + let mut client = Client::new(transport); + client.validate()?; + Ok(client) } /// Connect to the mux daemon via TCP. -pub fn mux_tcp_connect( - host: &str, - port: u16, - timeout: Duration, -) -> anyhow::Result> { +pub fn mux_tcp_connect(host: &str, port: u16, timeout: Duration) -> anyhow::Result> { let transport = MuxTransport::tcp(host, port, timeout)?; - Ok(Client::new(transport)) + let mut client = Client::new(transport); + client.validate()?; + Ok(client) } /// Auto-detect and connect to a DongLoRa device. @@ -84,15 +80,14 @@ pub fn mux_tcp_connect( /// 3. Direct USB serial (auto-detect by VID:PID, blocks until device appears) /// /// If `port` is `Some`, skips mux detection and connects directly to that serial port. -pub fn connect( - port: Option<&str>, - timeout: Duration, -) -> anyhow::Result> { +pub fn connect(port: Option<&str>, timeout: Duration) -> anyhow::Result> { // If explicit port given, go direct if let Some(port) = port { debug!("opening serial port {port}"); let transport = SerialTransport::open(port, timeout)?; - return Ok(Client::new(AnyTransport::Serial(transport))); + let mut client = Client::new(AnyTransport::Serial(transport)); + client.validate()?; + return Ok(client); } // Try TCP mux via environment variable @@ -100,7 +95,9 @@ pub fn connect( && let Some(transport) = try_tcp_mux(&tcp, timeout) { debug!("connected to TCP mux at {tcp}"); - return Ok(Client::new(AnyTransport::Mux(transport))); + let mut client = Client::new(AnyTransport::Mux(transport)); + client.validate()?; + return Ok(client); } // Try Unix socket mux @@ -109,7 +106,11 @@ pub fn connect( match MuxTransport::unix(&path, timeout) { Ok(transport) => { debug!("connected to mux socket at {path}"); - return Ok(Client::new(AnyTransport::Mux(transport))); + let mut client = Client::new(AnyTransport::Mux(transport)); + if client.validate().is_ok() { + return Ok(client); + } + debug!("mux at {path} did not validate, falling back to USB"); } Err(_) => { // Stale socket or connection refused — fall through @@ -119,11 +120,12 @@ pub fn connect( } // Direct USB serial — auto-detect or wait - let port_path = discovery::find_port() - .unwrap_or_else(discovery::wait_for_device); + let port_path = discovery::find_port().unwrap_or_else(discovery::wait_for_device); debug!("opening serial port {port_path}"); let transport = SerialTransport::open(&port_path, timeout)?; - Ok(Client::new(AnyTransport::Serial(transport))) + let mut client = Client::new(AnyTransport::Serial(transport)); + client.validate()?; + Ok(client) } /// Convenience: connect with default timeout. @@ -144,23 +146,71 @@ pub fn connect_mux_auto(timeout: Duration) -> anyhow::Result anyhow::Result> { + // Try TCP mux via environment variable + if let Ok(tcp) = std::env::var("DONGLORA_MUX_TCP") + && let Some(transport) = try_tcp_mux(&tcp, timeout) + { + debug!("connected to TCP mux at {tcp}"); + let mut client = Client::new(AnyTransport::Mux(transport)); + client.validate()?; + return Ok(client); + } + + // Try Unix socket mux + #[cfg(unix)] + if let Some(path) = find_mux_socket() { + match MuxTransport::unix(&path, timeout) { + Ok(transport) => { + debug!("connected to mux socket at {path}"); + let mut client = Client::new(AnyTransport::Mux(transport)); + if client.validate().is_ok() { + return Ok(client); + } + debug!("mux socket at {path} did not validate, falling back to USB"); + } + Err(_) => { + debug!("mux socket at {path} not reachable, falling back to USB"); + } + } + } + + // Direct USB serial — single non-blocking scan + let port_path = + discovery::find_port().ok_or_else(|| anyhow::anyhow!("no DongLoRa device found (no mux, no USB device)"))?; + debug!("opening serial port {port_path}"); + let transport = SerialTransport::open(&port_path, timeout)?; + let mut client = Client::new(AnyTransport::Serial(transport)); + client.validate()?; + Ok(client) +} + fn try_tcp_mux(addr: &str, timeout: Duration) -> Option { let (host, port) = if let Some((h, p)) = addr.rsplit_once(':') { let host = if h.is_empty() { "localhost" } else { h }; diff --git a/src/lib.rs b/src/lib.rs index ec67a35..cb81656 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! DongLoRa host library — connect, configure, send/receive LoRa packets. //! //! Implements the DongLoRa USB protocol (COBS-framed fixed-size LE). -//! See `firmware/PROTOCOL.md` for the full specification. +//! See the [`protocol`] module for wire types and constants. //! //! # Quick start //! @@ -9,7 +9,6 @@ //! use donglora_client::*; //! //! let mut client = connect_default()?; -//! client.ping()?; //! client.set_config(RadioConfig::default())?; //! client.start_rx()?; //! @@ -30,13 +29,12 @@ pub mod transport; // Flat re-exports for convenience pub use client::Client; -pub use codec::{decode_frame, encode_frame, read_frame, FrameReader}; -pub use connect::{connect, connect_default, connect_mux_auto, default_socket_path}; -pub use discovery::{find_port, wait_for_device, USB_PID, USB_VID}; +pub use codec::{FrameReader, decode_frame, encode_frame, read_frame}; +pub use connect::{connect, connect_default, connect_mux_auto, default_socket_path, try_connect}; +pub use discovery::{USB_PID, USB_VID, find_port, wait_for_device}; pub use protocol::{ - Bandwidth, Command, ErrorCode, RadioConfig, Response, CMD_TAG_SET_CONFIG, CMD_TAG_START_RX, - CMD_TAG_STOP_RX, ERROR_INVALID_CONFIG, MAX_PAYLOAD, PREAMBLE_DEFAULT, RADIO_CONFIG_SIZE, - RESP_TAG_ERROR, RESP_TAG_OK, RESP_TAG_RX_PACKET, TX_POWER_MAX, + Bandwidth, Command, ErrorCode, MAX_PAYLOAD, PREAMBLE_DEFAULT, RADIO_CONFIG_SIZE, RadioConfig, Response, + TX_POWER_MAX, }; pub use transport::{AnyTransport, MuxTransport, SerialTransport, Transport}; diff --git a/src/protocol.rs b/src/protocol.rs index a84ed18..9487096 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,7 +1,7 @@ //! Wire protocol types and fixed-size little-endian serialization. //! //! Mirrors the firmware's `protocol.rs` using std types. Every integer is -//! fixed-width LE. See `firmware/PROTOCOL.md` for the full specification. +//! fixed-width LE. /// Maximum LoRa payload size in bytes. pub const MAX_PAYLOAD: usize = 256; @@ -156,10 +156,7 @@ pub enum Command { SetConfig(RadioConfig), StartRx, StopRx, - Transmit { - config: Option, - payload: Vec, - }, + Transmit { config: Option, payload: Vec }, DisplayOn, DisplayOff, GetMac, @@ -241,10 +238,7 @@ impl Command { if rest.len() < data_start + len || len > MAX_PAYLOAD { return None; } - Some(Self::Transmit { - config, - payload: rest[data_start..data_start + len].to_vec(), - }) + Some(Self::Transmit { config, payload: rest[data_start..data_start + len].to_vec() }) } 6 => Some(Self::DisplayOn), 7 => Some(Self::DisplayOff), @@ -283,6 +277,8 @@ impl ErrorCode { } } +impl std::error::Error for ErrorCode {} + impl std::fmt::Display for ErrorCode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -303,11 +299,7 @@ impl std::fmt::Display for ErrorCode { pub enum Response { Pong, Config(RadioConfig), - RxPacket { - rssi: i16, - snr: i16, - payload: Vec, - }, + RxPacket { rssi: i16, snr: i16, payload: Vec }, TxDone, Ok, Error(ErrorCode), @@ -378,11 +370,7 @@ impl Response { if rest.len() < 6 + len || len > MAX_PAYLOAD { return None; } - Some(Self::RxPacket { - rssi, - snr, - payload: rest[6..6 + len].to_vec(), - }) + Some(Self::RxPacket { rssi, snr, payload: rest[6..6 + len].to_vec() }) } 3 => Some(Self::TxDone), 4 => Some(Self::Ok), @@ -403,6 +391,7 @@ impl Response { // ── Tests ────────────────────────────────────────────────────────── #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use super::*; @@ -494,30 +483,21 @@ mod tests { #[test] fn command_transmit_no_config_roundtrip() { - let cmd = Command::Transmit { - config: None, - payload: b"hello".to_vec(), - }; + let cmd = Command::Transmit { config: None, payload: b"hello".to_vec() }; let bytes = cmd.to_bytes(); assert_eq!(Command::from_bytes(&bytes), Some(cmd)); } #[test] fn command_transmit_with_config_roundtrip() { - let cmd = Command::Transmit { - config: Some(make_config()), - payload: b"test".to_vec(), - }; + let cmd = Command::Transmit { config: Some(make_config()), payload: b"test".to_vec() }; let bytes = cmd.to_bytes(); assert_eq!(Command::from_bytes(&bytes), Some(cmd)); } #[test] fn command_transmit_empty_payload() { - let cmd = Command::Transmit { - config: None, - payload: vec![], - }; + let cmd = Command::Transmit { config: None, payload: vec![] }; let bytes = cmd.to_bytes(); assert_eq!(Command::from_bytes(&bytes), Some(cmd)); } @@ -572,22 +552,14 @@ mod tests { #[test] fn response_rx_packet_roundtrip() { - let resp = Response::RxPacket { - rssi: -80, - snr: 10, - payload: b"data".to_vec(), - }; + let resp = Response::RxPacket { rssi: -80, snr: 10, payload: b"data".to_vec() }; let bytes = resp.to_bytes(); assert_eq!(Response::from_bytes(&bytes), Some(resp)); } #[test] fn response_rx_packet_empty_payload() { - let resp = Response::RxPacket { - rssi: -120, - snr: -5, - payload: vec![], - }; + let resp = Response::RxPacket { rssi: -120, snr: -5, payload: vec![] }; let bytes = resp.to_bytes(); assert_eq!(Response::from_bytes(&bytes), Some(resp)); } @@ -619,10 +591,7 @@ mod tests { fn response_tags() { assert_eq!(Response::Pong.tag(), 0); assert_eq!(Response::Config(make_config()).tag(), 1); - assert_eq!( - Response::RxPacket { rssi: 0, snr: 0, payload: vec![] }.tag(), - 2 - ); + assert_eq!(Response::RxPacket { rssi: 0, snr: 0, payload: vec![] }.tag(), 2); assert_eq!(Response::TxDone.tag(), 3); assert_eq!(Response::Ok.tag(), 4); assert_eq!(Response::Error(ErrorCode::RadioBusy).tag(), 5); @@ -685,9 +654,6 @@ mod tests { let cmd = Command::SetConfig(cfg); let bytes = cmd.to_bytes(); // Expected: 02 C0 CA 89 36 07 07 05 24 14 80 00 00 01 - assert_eq!( - bytes, - [0x02, 0xC0, 0xCA, 0x89, 0x36, 0x07, 0x07, 0x05, 0x24, 0x14, 0x80, 0x00, 0x00, 0x01] - ); + assert_eq!(bytes, [0x02, 0xC0, 0xCA, 0x89, 0x36, 0x07, 0x07, 0x05, 0x24, 0x14, 0x80, 0x00, 0x00, 0x01]); } } diff --git a/src/transport.rs b/src/transport.rs index 735f8cc..429dae0 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -61,9 +61,7 @@ impl Write for SerialTransport { impl Transport for SerialTransport { fn set_timeout(&mut self, timeout: Duration) -> anyhow::Result<()> { - self.port - .set_timeout(timeout) - .map_err(|e| anyhow::anyhow!("failed to set serial timeout: {e}"))?; + self.port.set_timeout(timeout).map_err(|e| anyhow::anyhow!("failed to set serial timeout: {e}"))?; self.timeout = timeout; Ok(()) } @@ -99,10 +97,7 @@ impl MuxTransport { stream .set_write_timeout(Some(timeout)) .map_err(|e| anyhow::anyhow!("failed to set socket write timeout: {e}"))?; - Ok(Self { - stream: MuxStream::Unix(stream), - timeout, - }) + Ok(Self { stream: MuxStream::Unix(stream), timeout }) } /// Connect to the mux daemon via TCP. @@ -117,16 +112,9 @@ impl MuxTransport { .ok_or_else(|| anyhow::anyhow!("no addresses found for {addr}"))?; let stream = std::net::TcpStream::connect_timeout(&sock_addr, timeout) .map_err(|e| anyhow::anyhow!("failed to connect to mux at {addr}: {e}"))?; - stream - .set_read_timeout(Some(timeout)) - .map_err(|e| anyhow::anyhow!("failed to set TCP read timeout: {e}"))?; - stream - .set_write_timeout(Some(timeout)) - .map_err(|e| anyhow::anyhow!("failed to set TCP write timeout: {e}"))?; - Ok(Self { - stream: MuxStream::Tcp(stream), - timeout, - }) + stream.set_read_timeout(Some(timeout)).map_err(|e| anyhow::anyhow!("failed to set TCP read timeout: {e}"))?; + stream.set_write_timeout(Some(timeout)).map_err(|e| anyhow::anyhow!("failed to set TCP write timeout: {e}"))?; + Ok(Self { stream: MuxStream::Tcp(stream), timeout }) } } @@ -163,12 +151,8 @@ impl Transport for MuxTransport { self.timeout = timeout; let result = match &self.stream { #[cfg(unix)] - MuxStream::Unix(s) => s - .set_read_timeout(Some(timeout)) - .and_then(|()| s.set_write_timeout(Some(timeout))), - MuxStream::Tcp(s) => s - .set_read_timeout(Some(timeout)) - .and_then(|()| s.set_write_timeout(Some(timeout))), + MuxStream::Unix(s) => s.set_read_timeout(Some(timeout)).and_then(|()| s.set_write_timeout(Some(timeout))), + MuxStream::Tcp(s) => s.set_read_timeout(Some(timeout)).and_then(|()| s.set_write_timeout(Some(timeout))), }; result.map_err(|e| anyhow::anyhow!("failed to set timeout: {e}")) }