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}