Skip to main content

can_motor_control/
bus.rs

1//! Bus = transport + codec + per-bus recv-id routing table.
2
3use std::collections::HashMap;
4
5use motor_codec::{BusCapabilities, MotorCodec, MotorTypeId};
6
7use crate::transport::CanBus;
8
9/// (group_name, motor_index) reached via a bus's recv-id routing table.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct RouteKey {
12    /// Group name that owns the target motor.
13    pub group_name: String,
14    /// Insertion-order index within that group.
15    pub motor_index: usize,
16}
17
18/// One CAN interface plus the vendor codec attached to it.
19///
20/// One [`Bus`] instance per physical interface; the codec is shared across
21/// every group attached to the bus, so inbound frames are decoded exactly
22/// once per frame.
23pub struct Bus {
24    pub(crate) transport: Box<dyn CanBus>,
25    pub(crate) codec: Box<dyn MotorCodec>,
26    pub(crate) routes: HashMap<u32, RouteKey>,
27}
28
29impl Bus {
30    /// Construct a bus and invoke `codec.bind_to_bus(transport.capabilities())`
31    /// exactly once.
32    pub fn new(transport: Box<dyn CanBus>, mut codec: Box<dyn MotorCodec>) -> Self {
33        codec.bind_to_bus(transport.capabilities());
34        Self {
35            transport,
36            codec,
37            routes: HashMap::new(),
38        }
39    }
40
41    /// Vendor short-name of the attached codec.
42    pub fn vendor(&self) -> &str {
43        self.codec.vendor_name()
44    }
45
46    /// Capabilities reported by the underlying transport.
47    pub fn capabilities(&self) -> BusCapabilities {
48        self.transport.capabilities()
49    }
50
51    /// True iff the bus's codec can encode/decode this motor type.
52    pub fn codec_supports(&self, mt: MotorTypeId) -> bool {
53        self.codec.supports(mt)
54    }
55
56    /// Read-only access to the per-bus recv-id routing table (populated by
57    /// `Robot::connect`).
58    pub fn routes(&self) -> &HashMap<u32, RouteKey> {
59        &self.routes
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use std::sync::atomic::{AtomicUsize, Ordering};
66    use std::sync::Arc;
67
68    use motor_codec::{
69        BusCapabilities, CanFrame, CodecError, Command, Event, Limits, MotorCodec, MotorRef,
70        MotorTypeId,
71    };
72
73    use super::*;
74    use crate::transport::MockCanBus;
75
76    /// Codec that counts how many times `bind_to_bus` is invoked.
77    pub(crate) struct CountingCodec {
78        pub binds: Arc<AtomicUsize>,
79        pub decodes: Arc<AtomicUsize>,
80    }
81    impl CountingCodec {
82        pub fn new() -> (Self, Arc<AtomicUsize>, Arc<AtomicUsize>) {
83            let b = Arc::new(AtomicUsize::new(0));
84            let d = Arc::new(AtomicUsize::new(0));
85            (
86                Self {
87                    binds: b.clone(),
88                    decodes: d.clone(),
89                },
90                b,
91                d,
92            )
93        }
94    }
95    impl MotorCodec for CountingCodec {
96        fn vendor_name(&self) -> &'static str {
97            "mock"
98        }
99        fn supports(&self, _: MotorTypeId) -> bool {
100            true
101        }
102        fn limits(&self, _: MotorTypeId) -> Result<Limits, CodecError> {
103            Ok(Limits {
104                p_max: 1.0,
105                v_max: 1.0,
106                t_max: 1.0,
107            })
108        }
109        fn bind_to_bus(&mut self, _: BusCapabilities) {
110            self.binds.fetch_add(1, Ordering::SeqCst);
111        }
112        fn encode_enable(&self, m: MotorRef<'_>) -> Result<CanFrame, CodecError> {
113            CanFrame::classical(m.send_id, &[0xFC])
114                .map_err(|_| CodecError::DecodeFailed { reason: "frame" })
115        }
116        fn encode_disable(&self, m: MotorRef<'_>) -> Result<CanFrame, CodecError> {
117            CanFrame::classical(m.send_id, &[0xFD])
118                .map_err(|_| CodecError::DecodeFailed { reason: "frame" })
119        }
120        fn encode_set_zero(&self, m: MotorRef<'_>) -> Result<CanFrame, CodecError> {
121            CanFrame::classical(m.send_id, &[0xFE])
122                .map_err(|_| CodecError::DecodeFailed { reason: "frame" })
123        }
124        fn encode_command(&self, m: MotorRef<'_>, _: &Command) -> Result<CanFrame, CodecError> {
125            CanFrame::classical(m.send_id, &[0x55])
126                .map_err(|_| CodecError::DecodeFailed { reason: "frame" })
127        }
128        fn decode(&self, frame: &CanFrame) -> Result<Option<Event>, CodecError> {
129            self.decodes.fetch_add(1, Ordering::SeqCst);
130            Ok(Some(Event::State {
131                motor_id: frame.id,
132                q: 0.0,
133                dq: 0.0,
134                tau: 0.0,
135                t_mos: 0,
136                t_rotor: 0,
137            }))
138        }
139    }
140
141    #[test]
142    fn bus_new_calls_bind_to_bus_once() {
143        let (codec, binds, _) = CountingCodec::new();
144        let _bus = Bus::new(Box::new(MockCanBus::new("m")), Box::new(codec));
145        assert_eq!(binds.load(Ordering::SeqCst), 1);
146    }
147
148    #[test]
149    fn bus_exposes_vendor_and_caps() {
150        let (codec, _, _) = CountingCodec::new();
151        let bus = Bus::new(Box::new(MockCanBus::new("m")), Box::new(codec));
152        assert_eq!(bus.vendor(), "mock");
153        assert_eq!(bus.capabilities(), BusCapabilities::classical());
154    }
155}