chore: release 1.0.0
This commit is contained in:
4 files changed
+84
-35
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.0-alpha.1 — 2026-04-21
|
||||
## 1.0.0 — 2026-04-22
|
||||
|
||||
### Breaking
|
||||
|
||||
|
||||
Generated
+4
-2
@@ -71,7 +71,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "donglora-client"
|
||||
version = "1.0.0-alpha.1"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"donglora-protocol",
|
||||
@@ -87,7 +87,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "donglora-protocol"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8f2743ac7668845500e3686890917cdf5b0a09d17ec099bde2db4d3fae2a4a4"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"ucobs",
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "donglora-client"
|
||||
version = "1.0.0-alpha.1"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.85"
|
||||
authors = ["Stephen Waits <steve@waits.net>"]
|
||||
@@ -13,7 +13,7 @@ categories = ["hardware-support"]
|
||||
exclude = ["CLAUDE_TODO.md", "deny.toml", "justfile", "mise.toml", "rustfmt.toml", ".jj"]
|
||||
|
||||
[dependencies]
|
||||
donglora-protocol = { path = "../protocol", default-features = false, features = ["std"] }
|
||||
donglora-protocol = { version = "1.0.0", default-features = false, features = ["std"] }
|
||||
thiserror = "2"
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "sync", "time", "macros", "io-util", "net"] }
|
||||
tokio-serial = "5"
|
||||
@@ -27,7 +27,7 @@ anyhow = "1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
# Direct access to raw wire primitives for tests (frame encoder/decoder
|
||||
# helpers that are not part of the donglora-client public API).
|
||||
donglora-protocol = { path = "../protocol", default-features = false, features = ["std"] }
|
||||
donglora-protocol = { version = "1.0.0", default-features = false, features = ["std"] }
|
||||
heapless = "0.9"
|
||||
|
||||
[[example]]
|
||||
|
||||
@@ -1,57 +1,104 @@
|
||||
# DongLoRa Rust Client
|
||||
|
||||
Rust client library for talking to a DongLoRa device — either directly
|
||||
over USB or through a mux daemon.
|
||||
[](https://crates.io/crates/donglora-client)
|
||||
[](https://docs.rs/donglora-client)
|
||||
|
||||
Async Rust client library for talking to a DongLoRa device, either
|
||||
directly over USB serial or through a mux daemon. Speaks DongLoRa
|
||||
Protocol v2 via the [`donglora-protocol`][dp] crate.
|
||||
|
||||
[dp]: https://crates.io/crates/donglora-protocol
|
||||
|
||||
## Install
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
donglora-client = "0.2"
|
||||
donglora-client = "1"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```rust
|
||||
use donglora_client::*;
|
||||
use donglora_client::connect;
|
||||
use donglora_protocol::{LoRaBandwidth, LoRaCodingRate, LoRaConfig, Modulation};
|
||||
|
||||
let mut client = connect_default()?;
|
||||
client.set_config(RadioConfig::default())?;
|
||||
client.start_rx()?;
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let mut dongle = connect(None, std::time::Duration::from_secs(5)).await?;
|
||||
|
||||
loop {
|
||||
if let Some(Response::RxPacket { rssi, snr, payload }) = client.recv()? {
|
||||
println!("RX rssi={rssi} snr={snr} len={}", payload.len());
|
||||
dongle.set_config(&Modulation::LoRa(LoRaConfig {
|
||||
freq_hz: 910_525_000,
|
||||
bw: LoRaBandwidth::Khz62,
|
||||
sf: 7,
|
||||
cr: LoRaCodingRate::Cr4_5,
|
||||
sync_word: 0x1424,
|
||||
tx_power_dbm: 20,
|
||||
..Default::default()
|
||||
})).await?;
|
||||
|
||||
dongle.start_rx().await?;
|
||||
while let Some(rx) = dongle.next_rx().await? {
|
||||
println!("RX rssi={} snr={} len={}", rx.rssi_tenths_dbm, rx.snr_tenths_db, rx.data.len());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
All `connect` functions automatically validate the device by pinging
|
||||
it, so you get a clear error immediately if the port is not 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<T>` with send/recv
|
||||
- `src/connect.rs` — auto-detection (mux socket, TCP, direct USB)
|
||||
- `src/dongle.rs`: high-level `Dongle` API (connect, set_config,
|
||||
transmit, start_rx, next_rx, tx_with_retry).
|
||||
- `src/session.rs`: reader task, tag-allocation, per-command response
|
||||
routing, async event fan-out.
|
||||
- `src/retry.rs`: `RetryPolicy` + randomized backoff for
|
||||
`CHANNEL_BUSY` / `EBUSY` per `PROTOCOL.md §6.10`.
|
||||
- `src/connect.rs`: auto-detection pipeline (TCP mux env var, unix
|
||||
socket mux, direct USB serial).
|
||||
- `src/transport.rs`: tokio transports (`tokio-serial` for USB,
|
||||
`tokio::net::{UnixStream, TcpStream}` for mux).
|
||||
- `src/discovery.rs`: USB VID:PID discovery + ping validation.
|
||||
- `src/errors.rs`: `ClientError` / `ClientResult<T>` (thiserror-based
|
||||
taxonomy mirroring the Python client's `DongloraError`).
|
||||
|
||||
## Connection Priority
|
||||
|
||||
[`connect`] and [`try_connect`] try these in order:
|
||||
`connect(None, timeout)` tries 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
|
||||
1. **TCP mux**: via `DONGLORA_MUX_TCP` env var (host:port).
|
||||
2. **Unix socket mux**: `$XDG_RUNTIME_DIR/donglora/mux.sock` or
|
||||
`/tmp/donglora-mux.sock`.
|
||||
3. **Direct USB serial**: auto-detects by VID:PID (CP210x, CH9102,
|
||||
CH340, FT232R bridge chips too).
|
||||
|
||||
`try_connect` returns an error immediately if no device is found.
|
||||
`connect` blocks until a USB device appears.
|
||||
Once a mux path wins, subsequent reconnects commit to that path and
|
||||
will not fall back to USB, preventing port-steal races during mux
|
||||
restart.
|
||||
|
||||
## Dependencies
|
||||
`try_connect(timeout)` returns an error immediately if no device is
|
||||
found; `connect` blocks until one appears.
|
||||
|
||||
- `ucobs` — COBS framing (same implementation as the firmware)
|
||||
- `serialport` — USB serial communication
|
||||
- `anyhow` — error handling
|
||||
- `tracing` — logging
|
||||
## TX with Retry
|
||||
|
||||
```rust
|
||||
use donglora_client::retry::RetryPolicy;
|
||||
|
||||
let policy = RetryPolicy::default(); // 3 attempts, 20-100 ms backoff, 500 ms cap
|
||||
let outcome = dongle.tx_with_retry(payload, &policy).await?;
|
||||
println!("sent in {} attempt(s)", outcome.attempts.len());
|
||||
```
|
||||
|
||||
## Keepalive
|
||||
|
||||
A background task pings the device every 500 ms when the host has
|
||||
been idle, tracking the 1 s inactivity window from `PROTOCOL.md §3.4`
|
||||
with a 2x safety margin. Disable via `ConnectOptions::keepalive(false)`
|
||||
if you want to manage liveness yourself.
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [`LICENSE`](LICENSE).
|
||||
Reference in new issue
Block a user