embedded_onewire/
search_async.rs

1use crate::{
2    OneWireAsync, OneWireSearchKind, OneWireStatus, error::OneWireError, utils::OneWireCrc,
3};
4
5/// A structure for asynchronous searching of devices on a 1-Wire bus.
6/// This structure implements the search algorithm for discovering devices on the 1-Wire bus.
7/// It maintains the state of the search.
8pub struct OneWireSearchAsync<'a, T> {
9    onewire: &'a mut T,
10    cmd: u8,
11    last_device: bool,
12    last_discrepancy: u8,
13    last_family_discrepancy: u8,
14    family: u8,
15    rom: [u8; 8],
16}
17
18impl<T> core::fmt::Debug for OneWireSearchAsync<'_, T> {
19    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
20        f.debug_struct("OneWireSearchAsync")
21            .field("cmd", &self.cmd)
22            .field("last_device", &self.last_device)
23            .field("last_discrepancy", &self.last_discrepancy)
24            .field("last_family_discrepancy", &self.last_family_discrepancy)
25            .field("family", &self.family)
26            .field("rom", &self.rom)
27            .finish()
28    }
29}
30
31impl<'a, T> OneWireSearchAsync<'a, T> {
32    /// Creates a new [OneWireSearchAsync] instance.
33    ///
34    /// # Arguments
35    /// * `onewire` - A mutable reference to a type that implements the `OneWire` trait.
36    /// * `cmd` - The command to use for the search operation (e.g., `0xf0` for normal search, `0xec` for search in alarm state).
37    pub fn new(onewire: &'a mut T, cmd: OneWireSearchKind) -> Self {
38        Self {
39            onewire,
40            cmd: cmd as _,
41            last_device: false,
42            last_discrepancy: 0,
43            last_family_discrepancy: 0,
44            family: 0, // Initialize family code to 0
45            rom: [0; 8],
46        }
47    }
48
49    /// Creates a new [`OneWireSearchAsync`] instance with a specific family code.
50    /// # Arguments
51    /// * `onewire` - A mutable reference to a type that implements the `OneWire` trait.
52    /// * `cmd` - The command to use for the search operation (e.g., `0xf0` for normal search, `0xec` for search in alarm state).
53    /// * `family` - The family code of the devices to search for.
54    pub fn with_family(onewire: &'a mut T, cmd: OneWireSearchKind, family: u8) -> Self {
55        let rom = [family, 0, 0, 0, 0, 0, 0, 0]; // Initialize the ROM with the family code
56        Self {
57            onewire,
58            cmd: cmd as _,
59            last_device: false,
60            last_discrepancy: 0,
61            last_family_discrepancy: 0,
62            family,
63            rom,
64        }
65    }
66
67    /// Resets the search state.
68    fn reset(&mut self) {
69        self.last_device = false; // Reset the last device flag
70        self.last_discrepancy = 0; // Reset the last discrepancy
71        self.last_family_discrepancy = 0; // Reset the last family discrepancy
72        self.rom = [self.family, 0, 0, 0, 0, 0, 0, 0]; // Reset the ROM array
73    }
74}
75
76impl<T: OneWireAsync> OneWireSearchAsync<'_, T> {
77    /// Searches for devices on the 1-Wire bus.
78    /// This method implements the [1-Wire search algorithm](https://www.analog.com/en/resources/app-notes/1wire-search-algorithm.html) to discover devices connected to the bus.
79    /// The [next](OneWireSearchAsync::next) method can be called repeatedly to find all devices on the bus.
80    /// At the end of the search, calling this method will return `None` to indicate that no more devices are present.
81    /// At that point, the search state becomes unusable and should be dropped.
82    /// The search state is reset if the [verify](OneWireSearchAsync::verify) method is called.
83    ///
84    /// # Returns
85    /// A result containing the ROM code of the found device as a `u64` value.
86    ///  
87    /// | Bit | Description |
88    /// |-----|-------------|
89    /// | 0-7 | Family code (e.g., 0x28 for DS18B20) |
90    /// | 8-15 | Serial number (first byte) |
91    /// | 16-23 | Serial number (second byte) |
92    /// | 24-31 | Serial number (third byte) |
93    /// | 32-39 | Serial number (fourth byte) |
94    /// | 40-47 | Serial number (fifth byte) |
95    /// | 48-55 | Serial number (sixth byte) |
96    /// | 56-63 | CRC-8 (`0b1_0001_1001` poly) |
97    #[allow(clippy::should_implement_trait)]
98    pub async fn next(&mut self) -> Result<Option<u64>, OneWireError<T::BusError>> {
99        if self.onewire.get_overdrive_mode() {
100            return Err(OneWireError::BusInvalidSpeed);
101        }
102        if self.last_device {
103            return Ok(None);
104        }
105        let status = self.onewire.reset().await?;
106        if !status.presence() {
107            return Err(OneWireError::NoDevicePresent);
108        }
109        if status.shortcircuit() {
110            return Err(OneWireError::ShortCircuit);
111        }
112        let mut id_bit_num: u8 = 1;
113        let mut last_zero: u8 = 0;
114        let mut idx: usize = 0; // Index in the ROM array
115        let mut rom_mask: u8 = 1; // Mask for the current bit in the ROM byte
116        self.onewire.write_byte(self.cmd).await?; // Search ROM command
117        let res = loop {
118            // Read the id_bit and the complement_bit using triplet if available
119            // and if this is not the first spin of the loop.
120            // If triplet is not implemented, fallback to reading bits, and let
121            // the write flag indicate if we need to write the direction bit later.
122            #[cfg(feature = "triplet-read")]
123            let (id_bit, complement_bit, dir) = { self.onewire.read_triplet().await? };
124            #[cfg(not(feature = "triplet-read"))]
125            let (id_bit, complement_bit) = {
126                let id_bit = self.onewire.read_bit().await?;
127                let complement_bit = self.onewire.read_bit().await?;
128                (id_bit, complement_bit)
129            };
130            if id_bit && complement_bit {
131                // Both bits are 1, which is an error condition, reset the search
132                break false;
133            }
134            let set = if id_bit != complement_bit {
135                // The bits are different, use the id_bit
136                id_bit
137            } else {
138                #[cfg(not(feature = "triplet-read"))]
139                {
140                    // Both bits are 0, use the direction from the ROM
141                    let idir = if id_bit_num < self.last_discrepancy {
142                        self.rom[idx] & rom_mask > 0
143                    } else {
144                        id_bit_num == self.last_discrepancy
145                    };
146                    if !idir {
147                        last_zero = id_bit_num;
148                        if last_zero < 9 {
149                            self.last_family_discrepancy = last_zero;
150                        }
151                    }
152                    idir
153                }
154                #[cfg(feature = "triplet-read")]
155                {
156                    if !dir {
157                        last_zero = id_bit_num;
158                        if last_zero < 9 {
159                            self.last_family_discrepancy = last_zero;
160                        }
161                    }
162                    dir
163                }
164            };
165            if set {
166                self.rom[idx] |= rom_mask; // Set the bit in the ROM
167            } else {
168                self.rom[idx] &= !rom_mask; // Clear the bit in the ROM
169            }
170            #[cfg(not(feature = "triplet-read"))]
171            self.onewire.write_bit(set).await?; // Write the direction bit if triplet is not implemented
172
173            id_bit_num += 1;
174            rom_mask <<= 1; // Move to the next bit in the ROM byte
175
176            if rom_mask == 0 {
177                idx += 1; // Move to the next byte in the ROM
178                rom_mask = 1; // Reset the mask for the next byte
179            }
180            if id_bit_num > 64 {
181                self.last_discrepancy = last_zero;
182                self.last_device = self.last_discrepancy == 0;
183                break true;
184            }
185        };
186
187        if !res || self.rom[0] == 0 {
188            // If no device was found or the first byte is zero, reset the search state
189            return Ok(None);
190        }
191        if !OneWireCrc::validate(&self.rom) {
192            // If the CRC is not valid, reset the search state
193            return Err(OneWireError::InvalidCrc);
194        }
195        if self.family != 0 && self.rom[0] != self.family {
196            // If a specific family code was set and it does not match the found device
197            return Ok(None);
198        }
199        Ok(Some(u64::from_le_bytes(self.rom)))
200    }
201
202    /// Verifies if the device with the given ROM code is present on the 1-Wire bus.
203    ///
204    /// This function should be called with a search state that has been exhausted (i.e., after calling [next](OneWireSearchAsync::next) until it returns `None`).
205    /// This functions resets the search state, and calling [next](OneWireSearchAsync::next) after this call will start a new search.
206    pub async fn verify(&mut self, rom: u64) -> Result<bool, OneWireError<T::BusError>> {
207        self.reset(); // Reset the search state
208        self.rom = rom.to_le_bytes(); // Set the ROM to verify
209        self.last_discrepancy = 64; // Set the last discrepancy to 64
210        let res = self.next().await?;
211        self.reset(); // Reset the search state after verification
212        Ok(res == Some(rom))
213    }
214}