Skip to main content

osdp/driver/
pd.rs

1//! PD (Peripheral Device) driver — receives commands from the ACU and
2//! emits replies. A real PD also owns the SCS state on the slave side.
3//!
4//! This is currently a thin scaffold: the dispatcher wires up incoming
5//! [`crate::command::Command`] values and delegates response generation to a
6//! caller-supplied handler.
7
8use crate::clock::Clock;
9use crate::command::{Command, CommandCode};
10use crate::error::Error;
11use crate::packet::{Address, ControlByte, CtrlFlags, PacketBuilder, ParsedPacket, Sqn};
12use crate::reply::Reply;
13use crate::transport::Transport;
14use alloc::vec::Vec;
15
16/// Trait for PD-side application logic. Each incoming command becomes a
17/// method call; the handler returns the reply to emit.
18pub trait PdHandler {
19    /// Dispatch a fully-decoded command and return the reply payload.
20    fn on_command(&mut self, command: &Command) -> Reply;
21}
22
23/// PD driver.
24pub struct Pd<T: Transport, C: Clock, H: PdHandler> {
25    transport: T,
26    #[allow(dead_code)]
27    clock: C,
28    /// PD's own bus address.
29    pub address: u8,
30    /// Whether to advertise CRC trailers in replies.
31    pub use_crc: bool,
32    handler: H,
33    rx_buf: Vec<u8>,
34    /// Last SQN we acted upon.
35    last_sqn: Option<u8>,
36    /// Last reply we sent (so we can repeat it on a duplicate SQN).
37    last_reply: Option<Vec<u8>>,
38}
39
40impl<T: Transport, C: Clock, H: PdHandler> Pd<T, C, H> {
41    /// New driver.
42    pub fn new(transport: T, clock: C, address: u8, handler: H) -> Self {
43        Self {
44            transport,
45            clock,
46            address,
47            use_crc: true,
48            handler,
49            rx_buf: Vec::with_capacity(crate::MAX_BUS_PACKET),
50            last_sqn: None,
51            last_reply: None,
52        }
53    }
54
55    /// Borrow the underlying transport.
56    pub fn transport(&mut self) -> &mut T {
57        &mut self.transport
58    }
59
60    /// Drain whatever bytes the transport has, dispatch the next complete
61    /// command, and reply on the wire if there is one.
62    ///
63    /// Returns `Ok(true)` if a packet was processed, `Ok(false)` if more
64    /// bytes are required.
65    pub fn poll_once(&mut self) -> Result<bool, Error> {
66        let mut tmp = [0u8; 64];
67        let n = self.transport.read(&mut tmp)?;
68        if n > 0 {
69            self.rx_buf.extend_from_slice(&tmp[..n]);
70        }
71
72        while let Some(som_pos) = self.rx_buf.iter().position(|&b| b == crate::SOM) {
73            self.rx_buf.drain(..som_pos);
74            match ParsedPacket::parse(&self.rx_buf) {
75                Ok((parsed, used)) => {
76                    let pd_addr = parsed.addr.pd_addr();
77                    if pd_addr != self.address && !parsed.addr.is_broadcast() {
78                        // Not for us — drop and resume.
79                        self.rx_buf.drain(..used);
80                        continue;
81                    }
82                    let code = CommandCode::from_byte(parsed.code)?;
83                    let sqn = parsed.ctrl.sqn.value();
84                    let cmd_data = parsed.data.to_vec();
85                    let used_len = used;
86                    self.rx_buf.drain(..used_len);
87
88                    if Some(sqn) == self.last_sqn {
89                        // ACU is asking for a reply repeat.
90                        if let Some(reply) = &self.last_reply {
91                            let r = reply.clone();
92                            self.transport.write_all(&r)?;
93                        }
94                        return Ok(true);
95                    }
96
97                    let command = Command::decode(code, &cmd_data)?;
98                    let reply = self.handler.on_command(&command);
99                    let bytes = self.encode_reply(sqn, &reply)?;
100                    self.transport.write_all(&bytes)?;
101                    self.last_sqn = Some(sqn);
102                    self.last_reply = Some(bytes);
103                    let _ = self.clock.now_ms();
104                    return Ok(true);
105                }
106                Err(Error::Truncated { .. }) => return Ok(false),
107                Err(Error::BadSom(_)) => {
108                    self.rx_buf.remove(0);
109                    continue;
110                }
111                Err(other) => return Err(other),
112            }
113        }
114
115        Ok(false)
116    }
117
118    fn encode_reply(&self, sqn: u8, reply: &Reply) -> Result<Vec<u8>, Error> {
119        let addr = Address::reply(self.address)?;
120        let ctrl = ControlByte::new(
121            Sqn::new(sqn)?,
122            if self.use_crc {
123                CtrlFlags::USE_CRC
124            } else {
125                CtrlFlags::empty()
126            },
127        );
128        let data = reply.encode_data()?;
129        PacketBuilder::plain(addr, ctrl, reply.code().as_byte(), data).encode()
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::clock::MockClock;
137    use crate::command::Command;
138    use crate::reply::{Ack, Reply};
139    use crate::transport::VecTransport;
140
141    struct AlwaysAck;
142    impl PdHandler for AlwaysAck {
143        fn on_command(&mut self, _command: &Command) -> Reply {
144            Reply::Ack(Ack)
145        }
146    }
147
148    #[test]
149    fn dispatches_poll_to_ack() {
150        let clock = MockClock::new();
151        let mut transport = VecTransport::new();
152        // Encode a POLL ourselves and feed it to the PD's incoming queue.
153        let bytes = PacketBuilder::plain(
154            Address::pd(0x05).unwrap(),
155            ControlByte::new(Sqn::new(1).unwrap(), CtrlFlags::USE_CRC),
156            CommandCode::Poll.as_byte(),
157            Vec::new(),
158        )
159        .encode()
160        .unwrap();
161        transport.feed(&bytes);
162        let mut pd = Pd::new(transport, clock, 0x05, AlwaysAck);
163        assert!(pd.poll_once().unwrap());
164        let reply_bytes: Vec<u8> = pd.transport().outgoing.drain(..).collect();
165        let (parsed, _) = ParsedPacket::parse(&reply_bytes).unwrap();
166        assert_eq!(parsed.code, 0x40);
167        assert!(parsed.addr.is_reply());
168    }
169}