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
|
# Changelog
|
||||||
|
|
||||||
## 1.0.0-alpha.1 — 2026-04-21
|
## 1.0.0 — 2026-04-22
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
|
|||||||
Generated
+4
-2
@@ -71,7 +71,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "donglora-client"
|
name = "donglora-client"
|
||||||
version = "1.0.0-alpha.1"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"donglora-protocol",
|
"donglora-protocol",
|
||||||
@@ -87,7 +87,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "donglora-protocol"
|
name = "donglora-protocol"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8f2743ac7668845500e3686890917cdf5b0a09d17ec099bde2db4d3fae2a4a4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heapless",
|
"heapless",
|
||||||
"ucobs",
|
"ucobs",
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "donglora-client"
|
name = "donglora-client"
|
||||||
version = "1.0.0-alpha.1"
|
version = "1.0.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.85"
|
rust-version = "1.85"
|
||||||
authors = ["Stephen Waits <steve@waits.net>"]
|
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"]
|
exclude = ["CLAUDE_TODO.md", "deny.toml", "justfile", "mise.toml", "rustfmt.toml", ".jj"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
donglora-protocol = { path = "../protocol", default-features = false, features = ["std"] }
|
donglora-protocol = { version = "1.0.0", default-features = false, features = ["std"] }
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "sync", "time", "macros", "io-util", "net"] }
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "sync", "time", "macros", "io-util", "net"] }
|
||||||
tokio-serial = "5"
|
tokio-serial = "5"
|
||||||
@@ -27,7 +27,7 @@ anyhow = "1"
|
|||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
# Direct access to raw wire primitives for tests (frame encoder/decoder
|
# Direct access to raw wire primitives for tests (frame encoder/decoder
|
||||||
# helpers that are not part of the donglora-client public API).
|
# 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"
|
heapless = "0.9"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
|||||||
@@ -1,57 +1,104 @@
|
|||||||
# DongLoRa Rust Client
|
# DongLoRa Rust Client
|
||||||
|
|
||||||
Rust client library for talking to a DongLoRa device — either directly
|
[](https://crates.io/crates/donglora-client)
|
||||||
over USB or through a mux daemon.
|
[](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
|
## Install
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
donglora-client = "0.2"
|
donglora-client = "1"
|
||||||
|
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use donglora_client::*;
|
use donglora_client::connect;
|
||||||
|
use donglora_protocol::{LoRaBandwidth, LoRaCodingRate, LoRaConfig, Modulation};
|
||||||
|
|
||||||
let mut client = connect_default()?;
|
#[tokio::main]
|
||||||
client.set_config(RadioConfig::default())?;
|
async fn main() -> anyhow::Result<()> {
|
||||||
client.start_rx()?;
|
let mut dongle = connect(None, std::time::Duration::from_secs(5)).await?;
|
||||||
|
|
||||||
loop {
|
dongle.set_config(&Modulation::LoRa(LoRaConfig {
|
||||||
if let Some(Response::RxPacket { rssi, snr, payload }) = client.recv()? {
|
freq_hz: 910_525_000,
|
||||||
println!("RX rssi={rssi} snr={snr} len={}", payload.len());
|
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,
|
All `connect` functions automatically validate the device by pinging
|
||||||
so you'll get a clear error immediately if the port isn't a real DongLoRa.
|
it, so you get a clear error immediately if the port is not a real
|
||||||
|
DongLoRa.
|
||||||
|
|
||||||
## What's in Here
|
## What's in Here
|
||||||
|
|
||||||
- `src/protocol.rs` — wire protocol types (`RadioConfig`, `Command`, `Response`, `ErrorCode`)
|
- `src/dongle.rs`: high-level `Dongle` API (connect, set_config,
|
||||||
- `src/codec.rs` — COBS framing, frame accumulator
|
transmit, start_rx, next_rx, tx_with_retry).
|
||||||
- `src/discovery.rs` — USB VID:PID device discovery
|
- `src/session.rs`: reader task, tag-allocation, per-command response
|
||||||
- `src/transport.rs` — serial and mux socket transports
|
routing, async event fan-out.
|
||||||
- `src/client.rs` — high-level `Client<T>` with send/recv
|
- `src/retry.rs`: `RetryPolicy` + randomized backoff for
|
||||||
- `src/connect.rs` — auto-detection (mux socket, TCP, direct USB)
|
`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
|
## 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
|
1. **TCP mux**: via `DONGLORA_MUX_TCP` env var (host:port).
|
||||||
2. **Unix socket mux** — checks `$XDG_RUNTIME_DIR/donglora/mux.sock` or `/tmp/donglora-mux.sock`
|
2. **Unix socket mux**: `$XDG_RUNTIME_DIR/donglora/mux.sock` or
|
||||||
3. **Direct USB serial** — auto-detects by VID:PID
|
`/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.
|
Once a mux path wins, subsequent reconnects commit to that path and
|
||||||
`connect` blocks until a USB device appears.
|
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)
|
## TX with Retry
|
||||||
- `serialport` — USB serial communication
|
|
||||||
- `anyhow` — error handling
|
```rust
|
||||||
- `tracing` — logging
|
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