1use crate::clock::Clock;
8use crate::command::Command;
9use crate::error::Error;
10use crate::packet::{Address, ControlByte, CtrlFlags, PacketBuilder, ParsedPacket, Sqn};
11use crate::reply::{Reply, ReplyCode};
12use crate::transport::Transport;
13use alloc::vec::Vec;
14
15#[derive(Debug, Clone)]
17pub struct PdState {
18 pub next_sqn: Sqn,
20 pub use_crc: bool,
22 pub last_seen_ms: u64,
24 seen_at_least_once: bool,
26}
27
28impl Default for PdState {
29 fn default() -> Self {
30 Self {
31 next_sqn: Sqn::ZERO,
32 use_crc: true,
33 last_seen_ms: 0,
34 seen_at_least_once: false,
35 }
36 }
37}
38
39impl PdState {
40 pub fn bump_sqn(&mut self) {
42 self.next_sqn = self.next_sqn.next();
43 }
44
45 pub fn mark_seen(&mut self, now_ms: u64) {
47 self.last_seen_ms = now_ms;
48 self.seen_at_least_once = true;
49 }
50
51 pub fn is_offline(&self, now_ms: u64) -> bool {
56 self.seen_at_least_once
57 && now_ms.saturating_sub(self.last_seen_ms) >= crate::OFFLINE_THRESHOLD_MS as u64
58 }
59
60 pub fn reset(&mut self) {
63 *self = Self::default();
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69#[non_exhaustive]
70pub enum ExchangeOutcome {
71 Reply(Reply),
73 Busy,
75 Timeout,
77 Offline,
79}
80
81#[derive(Debug, Clone, Copy)]
83pub struct RetryConfig {
84 pub max_retries: u8,
86 pub overall_budget_ms: u32,
90}
91
92impl Default for RetryConfig {
93 fn default() -> Self {
94 Self {
95 max_retries: 2,
96 overall_budget_ms: 0,
97 }
98 }
99}
100
101pub struct Acu<T: Transport, C: Clock> {
103 transport: T,
104 clock: C,
105 pub reply_delay_ms: u32,
107 pub retry: RetryConfig,
109 rx_buf: Vec<u8>,
110}
111
112impl<T: Transport, C: Clock> Acu<T, C> {
113 pub fn new(transport: T, clock: C) -> Self {
115 Self {
116 transport,
117 clock,
118 reply_delay_ms: crate::REPLY_DELAY_MS,
119 retry: RetryConfig::default(),
120 rx_buf: Vec::with_capacity(crate::MAX_BUS_PACKET),
121 }
122 }
123
124 pub fn transport(&mut self) -> &mut T {
126 &mut self.transport
127 }
128
129 pub fn clock(&self) -> &C {
131 &self.clock
132 }
133
134 pub fn send_to(
136 &mut self,
137 pd_addr: u8,
138 pd: &mut PdState,
139 command: &Command,
140 ) -> Result<Vec<u8>, Error> {
141 self.send_with_sqn(pd_addr, pd.use_crc, pd.next_sqn, command)
142 }
143
144 fn send_with_sqn(
145 &mut self,
146 pd_addr: u8,
147 use_crc: bool,
148 sqn: Sqn,
149 command: &Command,
150 ) -> Result<Vec<u8>, Error> {
151 let addr = Address::pd(pd_addr)?;
152 let mut flags = CtrlFlags::empty();
153 if use_crc {
154 flags |= CtrlFlags::USE_CRC;
155 }
156 let ctrl = ControlByte::new(sqn, flags);
157 let data = command.encode_data()?;
158 let bytes = PacketBuilder::plain(addr, ctrl, command.code().as_byte(), data).encode()?;
159 self.transport.write_all(&bytes)?;
160 Ok(bytes)
161 }
162
163 pub fn receive(&mut self, pd: &mut PdState) -> Result<Reply, Error> {
173 let start = self.clock.now_ms();
174 let mut empty_reads = 0u8;
175 loop {
176 if let Some((reply_code, data)) = self.try_parse_packet()? {
177 let now = self.clock.now_ms();
178 pd.mark_seen(now);
179 pd.bump_sqn();
180 return Reply::decode(reply_code, &data);
181 }
182 let mut tmp = [0u8; 64];
183 let n = self.transport.read(&mut tmp)?;
184 if n > 0 {
185 self.rx_buf.extend_from_slice(&tmp[..n]);
186 empty_reads = 0;
187 continue;
188 }
189 if self.clock.now_ms().saturating_sub(start) >= self.reply_delay_ms as u64 {
190 return Err(Error::Timeout);
191 }
192 empty_reads = empty_reads.saturating_add(1);
193 if empty_reads >= 4 {
197 return Err(Error::Timeout);
198 }
199 }
200 }
201
202 pub fn exchange(
214 &mut self,
215 pd_addr: u8,
216 pd: &mut PdState,
217 command: &Command,
218 ) -> Result<ExchangeOutcome, Error> {
219 let now = self.clock.now_ms();
220 if pd.is_offline(now) {
221 return Ok(ExchangeOutcome::Offline);
222 }
223
224 let started = now;
225 let sqn = pd.next_sqn;
226 let mut attempts: u8 = 0;
227 let max = self.retry.max_retries;
228 let budget = self.retry.overall_budget_ms;
229
230 loop {
231 self.send_with_sqn(pd_addr, pd.use_crc, sqn, command)?;
232 match self.recv_one_with_sqn(sqn) {
233 Ok(reply) => {
234 let now = self.clock.now_ms();
235 pd.mark_seen(now);
236 if matches!(reply, Reply::Busy(_)) {
237 return Ok(ExchangeOutcome::Busy);
239 }
240 pd.bump_sqn();
241 return Ok(ExchangeOutcome::Reply(reply));
242 }
243 Err(Error::Timeout) => {
244 attempts += 1;
245 let now = self.clock.now_ms();
246 if pd.is_offline(now) {
247 return Ok(ExchangeOutcome::Offline);
248 }
249 if attempts > max {
250 return Ok(ExchangeOutcome::Timeout);
251 }
252 if budget != 0 && now.saturating_sub(started) >= budget as u64 {
253 return Ok(ExchangeOutcome::Timeout);
254 }
255 continue;
257 }
258 Err(other) => return Err(other),
259 }
260 }
261 }
262
263 fn recv_one_with_sqn(&mut self, _expected_sqn: Sqn) -> Result<Reply, Error> {
266 let start = self.clock.now_ms();
267 let mut empty_reads = 0u8;
268 loop {
269 if let Some((reply_code, data)) = self.try_parse_packet()? {
270 return Reply::decode(reply_code, &data);
271 }
272 let mut tmp = [0u8; 64];
273 let n = self.transport.read(&mut tmp)?;
274 if n > 0 {
275 self.rx_buf.extend_from_slice(&tmp[..n]);
276 empty_reads = 0;
277 continue;
278 }
279 if self.clock.now_ms().saturating_sub(start) >= self.reply_delay_ms as u64 {
280 return Err(Error::Timeout);
281 }
282 empty_reads = empty_reads.saturating_add(1);
283 if empty_reads >= 4 {
284 return Err(Error::Timeout);
285 }
286 }
287 }
288
289 fn try_parse_packet(&mut self) -> Result<Option<(ReplyCode, Vec<u8>)>, Error> {
290 while let Some(som_pos) = self.rx_buf.iter().position(|&b| b == crate::SOM) {
291 self.rx_buf.drain(..som_pos);
292 match ParsedPacket::parse(&self.rx_buf) {
293 Ok((parsed, used)) => {
294 let code = ReplyCode::from_byte(parsed.code)?;
295 let data = parsed.data.to_vec();
296 self.rx_buf.drain(..used);
297 return Ok(Some((code, data)));
298 }
299 Err(Error::Truncated { .. }) => return Ok(None),
300 Err(Error::BadSom(_)) => {
301 self.rx_buf.remove(0);
302 continue;
303 }
304 Err(Error::BadCrc { .. }) | Err(Error::BadChecksum { .. }) => {
306 self.rx_buf.remove(0);
307 continue;
308 }
309 Err(other) => return Err(other),
310 }
311 }
312 Ok(None)
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use crate::clock::MockClock;
320 use crate::command::Poll;
321 use crate::transport::VecTransport;
322
323 #[test]
324 fn send_poll_emits_correct_bytes() {
325 let clock = MockClock::new();
326 let transport = VecTransport::new();
327 let mut acu = Acu::new(transport, clock);
328 let mut pd = PdState::default();
329 let bytes = acu.send_to(0x05, &mut pd, &Command::Poll(Poll)).unwrap();
330 assert_eq!(bytes[0], crate::SOM);
331 assert_eq!(bytes[1], 0x05);
332 assert_eq!(bytes[4] & 0x0F, 0x04); }
334
335 #[test]
336 fn exchange_offline_when_silent() {
337 let clock = MockClock::new();
338 let transport = VecTransport::new();
339 let mut acu = Acu::new(transport, clock.clone());
340 acu.retry = RetryConfig {
341 max_retries: 0,
342 overall_budget_ms: 0,
343 };
344 let mut pd = PdState::default();
345 pd.mark_seen(0);
347 clock.set(crate::OFFLINE_THRESHOLD_MS as u64 + 1);
348 let outcome = acu.exchange(0x05, &mut pd, &Command::Poll(Poll)).unwrap();
349 assert_eq!(outcome, ExchangeOutcome::Offline);
350 }
351
352 #[test]
353 fn timeout_then_no_more_retries_returns_timeout() {
354 let clock = MockClock::new();
355 let transport = VecTransport::new();
356 let mut acu = Acu::new(transport, clock.clone());
357 acu.retry = RetryConfig {
358 max_retries: 0,
359 overall_budget_ms: 0,
360 };
361 let mut pd = PdState::default();
362 pd.mark_seen(0);
363 clock.set(crate::REPLY_DELAY_MS as u64 + 1);
366 let outcome = acu.exchange(0x05, &mut pd, &Command::Poll(Poll)).unwrap();
367 assert_eq!(outcome, ExchangeOutcome::Timeout);
368 }
369}