donglora_client/
client.rs1use std::collections::VecDeque;
8use std::time::Duration;
9
10use crate::codec::{encode_frame, read_frame};
11use crate::protocol::{Command, ErrorCode, MAX_PAYLOAD, RadioConfig, Response};
12use crate::transport::Transport;
13
14const RX_BUFFER_CAP: usize = 256;
16
17const MAX_UNSOLICITED_BEFORE_TIMEOUT: usize = 50;
19
20const HANDSHAKE_TIMEOUT: Duration = Duration::from_millis(200);
22
23pub struct Client<T: Transport> {
27 transport: T,
28 rx_buffer: VecDeque<Response>,
29}
30
31impl<T: Transport> Client<T> {
32 pub fn new(transport: T) -> Self {
34 Self { transport, rx_buffer: VecDeque::with_capacity(64) }
35 }
36
37 pub fn send(&mut self, cmd: Command) -> anyhow::Result<Response> {
42 let frame = encode_frame(&cmd.to_bytes());
43 std::io::Write::write_all(&mut self.transport, &frame)?;
44 std::io::Write::flush(&mut self.transport)?;
45
46 for _ in 0..MAX_UNSOLICITED_BEFORE_TIMEOUT {
47 let data =
48 read_frame(&mut self.transport)?.ok_or_else(|| anyhow::anyhow!("timeout waiting for response"))?;
49 let resp = Response::from_bytes(&data).ok_or_else(|| {
50 let hex: Vec<String> = data.iter().map(|b| format!("{b:02x}")).collect();
51 anyhow::anyhow!("malformed response ({} bytes): [{}]", data.len(), hex.join(" "))
52 })?;
53 if resp.is_rx_packet() {
54 self.buffer_rx(resp);
55 continue;
56 }
57 return Ok(resp);
58 }
59 anyhow::bail!("no solicited response after {MAX_UNSOLICITED_BEFORE_TIMEOUT} frames")
60 }
61
62 pub fn recv(&mut self) -> anyhow::Result<Option<Response>> {
66 if let Some(pkt) = self.rx_buffer.pop_front() {
67 return Ok(Some(pkt));
68 }
69 let Some(data) = read_frame(&mut self.transport)? else {
70 return Ok(None);
71 };
72 let resp =
73 Response::from_bytes(&data).ok_or_else(|| anyhow::anyhow!("malformed response ({} bytes)", data.len()))?;
74 if resp.is_rx_packet() {
75 Ok(Some(resp))
76 } else {
77 tracing::warn!("recv: discarding unexpected unsolicited response: {resp:?}");
78 Ok(None)
79 }
80 }
81
82 pub fn drain_rx(&mut self) -> anyhow::Result<Vec<Response>> {
87 let mut packets: Vec<Response> = self.rx_buffer.drain(..).collect();
88
89 let old_timeout = self.transport.timeout();
90 self.transport.set_timeout(std::time::Duration::from_millis(10))?;
91
92 let result: anyhow::Result<()> = (|| {
93 loop {
94 let Some(data) = read_frame(&mut self.transport)? else {
95 return Ok(());
96 };
97 if let Some(resp) = Response::from_bytes(&data)
98 && resp.is_rx_packet()
99 {
100 packets.push(resp);
101 }
102 }
103 })();
104
105 let restore_result = self.transport.set_timeout(old_timeout);
108 result?;
109 restore_result?;
110 Ok(packets)
111 }
112
113 pub fn validate(&mut self) -> anyhow::Result<()> {
119 let saved = self.transport.timeout();
120 self.transport.set_timeout(HANDSHAKE_TIMEOUT)?;
121 let result = self.ping();
122 self.transport.set_timeout(saved)?;
123 result.map_err(|_| anyhow::anyhow!("device did not respond to ping — not a DongLoRa device"))
124 }
125
126 pub fn ping(&mut self) -> anyhow::Result<()> {
128 match self.send(Command::Ping)? {
129 Response::Pong => Ok(()),
130 other => anyhow::bail!("unexpected response to Ping: {other:?}"),
131 }
132 }
133
134 pub fn set_config(&mut self, config: RadioConfig) -> anyhow::Result<()> {
136 match self.send(Command::SetConfig(config))? {
137 Response::Ok => Ok(()),
138 Response::Error(code) => anyhow::bail!("SetConfig failed: {code}"),
139 other => anyhow::bail!("unexpected response to SetConfig: {other:?}"),
140 }
141 }
142
143 pub fn start_rx(&mut self) -> anyhow::Result<()> {
145 match self.send(Command::StartRx)? {
146 Response::Ok => Ok(()),
147 Response::Error(code) => anyhow::bail!("StartRx failed: {code}"),
148 other => anyhow::bail!("unexpected response to StartRx: {other:?}"),
149 }
150 }
151
152 pub fn stop_rx(&mut self) -> anyhow::Result<()> {
154 match self.send(Command::StopRx)? {
155 Response::Ok => Ok(()),
156 Response::Error(code) => anyhow::bail!("StopRx failed: {code}"),
157 other => anyhow::bail!("unexpected response to StopRx: {other:?}"),
158 }
159 }
160
161 pub fn transmit(&mut self, payload: &[u8], config: Option<RadioConfig>) -> anyhow::Result<()> {
163 if payload.len() > MAX_PAYLOAD {
164 anyhow::bail!("payload too large ({} bytes, max {MAX_PAYLOAD})", payload.len());
165 }
166 let cmd = Command::Transmit { config, payload: payload.to_vec() };
167 match self.send(cmd)? {
168 Response::TxDone => Ok(()),
169 Response::Error(ErrorCode::TxTimeout) => anyhow::bail!("transmit timed out"),
170 Response::Error(code) => anyhow::bail!("Transmit failed: {code}"),
171 other => anyhow::bail!("unexpected response to Transmit: {other:?}"),
172 }
173 }
174
175 pub fn get_mac(&mut self) -> anyhow::Result<[u8; 6]> {
177 match self.send(Command::GetMac)? {
178 Response::MacAddress(mac) => Ok(mac),
179 Response::Error(code) => anyhow::bail!("GetMac failed: {code}"),
180 other => anyhow::bail!("unexpected response to GetMac: {other:?}"),
181 }
182 }
183
184 pub fn get_config(&mut self) -> anyhow::Result<RadioConfig> {
186 match self.send(Command::GetConfig)? {
187 Response::Config(cfg) => Ok(cfg),
188 Response::Error(code) => anyhow::bail!("GetConfig failed: {code}"),
189 other => anyhow::bail!("unexpected response to GetConfig: {other:?}"),
190 }
191 }
192
193 pub fn display_on(&mut self) -> anyhow::Result<()> {
195 match self.send(Command::DisplayOn)? {
196 Response::Ok => Ok(()),
197 Response::Error(code) => anyhow::bail!("DisplayOn failed: {code}"),
198 other => anyhow::bail!("unexpected response to DisplayOn: {other:?}"),
199 }
200 }
201
202 pub fn display_off(&mut self) -> anyhow::Result<()> {
204 match self.send(Command::DisplayOff)? {
205 Response::Ok => Ok(()),
206 Response::Error(code) => anyhow::bail!("DisplayOff failed: {code}"),
207 other => anyhow::bail!("unexpected response to DisplayOff: {other:?}"),
208 }
209 }
210
211 pub fn into_inner(self) -> T {
213 self.transport
214 }
215
216 pub fn transport(&self) -> &T {
218 &self.transport
219 }
220
221 pub fn transport_mut(&mut self) -> &mut T {
223 &mut self.transport
224 }
225
226 fn buffer_rx(&mut self, resp: Response) {
227 if self.rx_buffer.len() >= RX_BUFFER_CAP {
228 self.rx_buffer.pop_front(); }
230 self.rx_buffer.push_back(resp);
231 }
232}