rf24/radio/rf24/
auto_ack.rs

1use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
2
3use crate::radio::{prelude::EsbAutoAck, Nrf24Error, RF24};
4
5use super::{commands, registers, Feature};
6
7impl<SPI, DO, DELAY> EsbAutoAck for RF24<SPI, DO, DELAY>
8where
9    SPI: SpiDevice,
10    DO: OutputPin,
11    DELAY: DelayNs,
12{
13    type AutoAckErrorType = Nrf24Error<SPI::Error, DO::Error>;
14
15    fn set_ack_payloads(&mut self, enable: bool) -> Result<(), Self::AutoAckErrorType> {
16        if self._feature.ack_payloads() != enable {
17            self.spi_read(1, registers::FEATURE)?;
18            self._feature =
19                Feature::from_bits(self._feature.into_bits() & !Feature::REG_MASK | self._buf[1])
20                    .with_ack_payloads(enable);
21            self.spi_write_byte(
22                registers::FEATURE,
23                self._feature.into_bits() & Feature::REG_MASK,
24            )?;
25
26            if enable {
27                // Enable dynamic payload on all pipes
28                self.spi_write_byte(registers::DYNPD, 0x3F)?;
29            }
30            // else disable ack payloads, but leave dynamic payload features as is
31        }
32        Ok(())
33    }
34
35    fn get_ack_payloads(&self) -> bool {
36        self._feature.ack_payloads()
37    }
38
39    fn set_auto_ack(&mut self, enable: bool) -> Result<(), Self::AutoAckErrorType> {
40        self.spi_write_byte(registers::EN_AA, 0x3F * enable as u8)?;
41        // accommodate ACK payloads feature
42        if !enable && self._feature.ack_payloads() {
43            self.set_ack_payloads(false)?;
44        }
45        Ok(())
46    }
47
48    fn set_auto_ack_pipe(&mut self, enable: bool, pipe: u8) -> Result<(), Self::AutoAckErrorType> {
49        if pipe > 5 {
50            return Ok(());
51        }
52        self.spi_read(1, registers::EN_AA)?;
53        let mask = 1 << pipe;
54        let reg_val = self._buf[1];
55        if !enable && self._feature.ack_payloads() && pipe == 0 {
56            self.set_ack_payloads(enable)?;
57        }
58        self.spi_write_byte(registers::EN_AA, reg_val & !mask | (mask * enable as u8))
59    }
60
61    fn allow_ask_no_ack(&mut self, enable: bool) -> Result<(), Self::AutoAckErrorType> {
62        self.spi_read(1, registers::FEATURE)?;
63        self.spi_write_byte(registers::FEATURE, self._buf[1] & !1 | enable as u8)
64    }
65
66    fn write_ack_payload(&mut self, pipe: u8, buf: &[u8]) -> Result<bool, Self::AutoAckErrorType> {
67        if self._feature.ack_payloads() && pipe <= 5 {
68            let len = buf.len().min(32);
69            self.spi_write_buf(commands::W_ACK_PAYLOAD | pipe, &buf[..len])?;
70            return Ok(!self._status.tx_full());
71        }
72        Ok(false)
73    }
74
75    fn set_auto_retries(&mut self, delay: u8, count: u8) -> Result<(), Self::AutoAckErrorType> {
76        self.spi_write_byte(registers::SETUP_RETR, count.min(15) | (delay.min(15) << 4))
77    }
78}
79
80/////////////////////////////////////////////////////////////////////////////////
81/// unit tests
82#[cfg(test)]
83mod test {
84    extern crate std;
85    use crate::radio::prelude::{EsbAutoAck, EsbPayloadLength};
86    use crate::spi_test_expects;
87
88    use super::{commands, registers, RF24};
89    use embedded_hal_mock::eh1::delay::NoopDelay;
90    use embedded_hal_mock::eh1::digital::Mock as PinMock;
91    use embedded_hal_mock::eh1::spi::{Mock as SpiMock, Transaction as SpiTransaction};
92    use std::vec;
93
94    const EN_ACK_PAY: u8 = 1 << 1;
95    const EN_DPL: u8 = 1 << 2;
96
97    #[test]
98    pub fn allow_ack_payloads() {
99        // Create pin
100        let pin_expectations = [];
101        let mut pin_mock = PinMock::new(&pin_expectations);
102
103        // create delay fn
104        let delay_mock = NoopDelay::new();
105        let mut ack_buf = [0x55; 3];
106        let valid_pipe = 2;
107        ack_buf[0] = commands::W_ACK_PAYLOAD | valid_pipe;
108
109        let spi_expectations = spi_test_expects![
110            // enable ACK payloads
111            // read/write FEATURE register
112            (vec![registers::FEATURE, 0u8], vec![0xEu8, 0u8]),
113            (
114                vec![
115                    registers::FEATURE | commands::W_REGISTER,
116                    EN_ACK_PAY | EN_DPL,
117                ],
118                vec![0xEu8, 0u8],
119            ),
120            // write DYNPD register
121            (
122                vec![registers::DYNPD | commands::W_REGISTER, 0x3Fu8],
123                vec![0xEu8, 0u8],
124            ),
125            // write_ack_payload()
126            (ack_buf.to_vec(), vec![0u8; 3]),
127            // write_ack_payload() again but with TX FIFO as full
128            (ack_buf.to_vec(), vec![1u8; 3]),
129            // read EN_AA register value
130            (vec![registers::EN_AA, 1u8], vec![0u8, 0x3Fu8]),
131            // disable ACK payloads in FEATURE register
132            (
133                vec![registers::FEATURE, 0x3Fu8],
134                vec![0u8, EN_ACK_PAY | EN_DPL | 1]
135            ),
136            (
137                vec![registers::FEATURE | commands::W_REGISTER, EN_DPL | 1],
138                vec![0xEu8, 0u8],
139            ),
140            // set EN_AA register with pipe 0 disabled
141            (
142                vec![registers::EN_AA | commands::W_REGISTER, 0x3Eu8],
143                vec![0xEu8, 0u8],
144            ),
145            // read EN_AA register value
146            (vec![registers::EN_AA, 0u8], vec![0u8, 0x3Eu8]),
147            // set EN_AA register with pipes 0 and 1 disabled
148            (
149                vec![registers::EN_AA | commands::W_REGISTER, 0x3Cu8],
150                vec![0xEu8, 0u8],
151            ),
152        ];
153        let mut spi_mock = SpiMock::new(&spi_expectations);
154        let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock);
155        radio.set_ack_payloads(true).unwrap();
156        // do again for region coverage (should result in Ok non-op)
157        radio.set_ack_payloads(true).unwrap();
158        let buf = &ack_buf[1..3];
159        // write ACK payload to invalid pipe (results in Ok non-op)
160        assert!(!radio.write_ack_payload(9, buf).unwrap());
161        // write ACK payload to valid pipe (test will also mark TX FIFO as full)
162        assert!(radio.write_ack_payload(valid_pipe, buf).unwrap());
163        // write ACK payload to valid pipe with TX FIFO full
164        assert!(!radio.write_ack_payload(valid_pipe, buf).unwrap());
165        // disable invalid pipe number (results in Ok non-op)
166        radio.set_auto_ack_pipe(false, 9).unwrap();
167        // disable auto-ack on pipe 0 (also disables ack_payloads)
168        radio.set_auto_ack_pipe(false, 0).unwrap();
169        // disable pipe 1 for region coverage
170        radio.set_auto_ack_pipe(false, 1).unwrap();
171        spi_mock.done();
172        pin_mock.done();
173    }
174
175    #[test]
176    pub fn set_auto_ack() {
177        // Create pin
178        let pin_expectations = [];
179        let mut pin_mock = PinMock::new(&pin_expectations);
180
181        // create delay fn
182        let delay_mock = NoopDelay::new();
183
184        let spi_expectations = spi_test_expects![
185            // enable ACK payloads
186            // read/write FEATURE register
187            (vec![registers::FEATURE, 0u8], vec![0xEu8, 0u8]),
188            (
189                vec![
190                    registers::FEATURE | commands::W_REGISTER,
191                    EN_ACK_PAY | EN_DPL,
192                ],
193                vec![0xEu8, 0u8],
194            ),
195            // write DYNPD register
196            (
197                vec![registers::DYNPD | commands::W_REGISTER, 0x3Fu8],
198                vec![0xEu8, 0u8],
199            ),
200            // write EN_AA register value
201            (
202                vec![registers::EN_AA | commands::W_REGISTER, 0u8],
203                vec![0xEu8, 0u8],
204            ),
205            // disable ACK payloads in FEATURE register
206            (
207                vec![registers::FEATURE, 0u8],
208                vec![0u8, EN_ACK_PAY | EN_DPL]
209            ),
210            (
211                vec![registers::FEATURE | commands::W_REGISTER, EN_DPL],
212                vec![0xEu8, 0u8],
213            ),
214            // read RX_PL_WID
215            (vec![commands::R_RX_PL_WID, 0u8], vec![0xEu8, 32]),
216        ];
217        let mut spi_mock = SpiMock::new(&spi_expectations);
218        let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock);
219        radio.set_ack_payloads(true).unwrap();
220        assert!(radio.get_ack_payloads());
221        radio.set_auto_ack(false).unwrap();
222        assert_eq!(radio.get_dynamic_payload_length().unwrap(), 32u8);
223        spi_mock.done();
224        pin_mock.done();
225    }
226
227    #[test]
228    pub fn allow_ask_no_ack() {
229        // Create pin
230        let pin_expectations = [];
231        let mut pin_mock = PinMock::new(&pin_expectations);
232
233        // create delay fn
234        let delay_mock = NoopDelay::new();
235
236        let spi_expectations = spi_test_expects![
237            // disable EN_DYN_ACK flag in FEATURE register
238            (vec![registers::FEATURE, 0u8], vec![0u8, EN_ACK_PAY]),
239            (
240                vec![registers::FEATURE | commands::W_REGISTER, EN_ACK_PAY | 1],
241                vec![0xEu8, 0u8],
242            ),
243        ];
244        let mut spi_mock = SpiMock::new(&spi_expectations);
245        let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock);
246        radio.allow_ask_no_ack(true).unwrap();
247        spi_mock.done();
248        pin_mock.done();
249    }
250}