embedded_onewire/
search.rs

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