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