1use embedded_hal::delay::DelayNs;
4use pamoja_core::Sensor;
5use pamoja_hal::onewire::{OneWireBus, OneWireError, RomCode};
6
7use super::{command, Resolution, Scratchpad, EEPROM_WRITE_MICROS};
8use crate::error::DriverError;
9
10#[derive(Debug)]
52pub struct Ds18b20<B, D> {
53 bus: B,
54 delay: D,
55 rom: Option<RomCode>,
56 resolution: Resolution,
57 alarm_high: i8,
58 alarm_low: i8,
59}
60
61impl<B, D> Ds18b20<B, D> {
62 pub fn new(bus: B, delay: D) -> Self {
74 Ds18b20 {
75 bus,
76 delay,
77 rom: None,
78 resolution: Resolution::Bits12,
79 alarm_high: 125,
80 alarm_low: -55,
81 }
82 }
83
84 pub fn addressed(bus: B, delay: D, rom: RomCode) -> Self {
97 let mut probe = Ds18b20::new(bus, delay);
98 probe.rom = Some(rom);
99 probe
100 }
101
102 pub fn with_resolution(mut self, resolution: Resolution) -> Self {
114 self.resolution = resolution;
115 self
116 }
117
118 pub fn with_alarms(mut self, high: i8, low: i8) -> Self {
132 self.alarm_high = high;
133 self.alarm_low = low;
134 self
135 }
136
137 pub fn rom(&self) -> Option<RomCode> {
139 self.rom
140 }
141
142 pub fn resolution(&self) -> Resolution {
144 self.resolution
145 }
146
147 pub fn release(self) -> (B, D) {
153 (self.bus, self.delay)
154 }
155}
156
157impl<B: OneWireBus, D: DelayNs> Ds18b20<B, D> {
158 pub fn init(&mut self) -> Result<(), DriverError<OneWireError<B::Error>>> {
171 self.select()?;
172 let scratchpad = [
173 command::WRITE_SCRATCHPAD,
174 self.alarm_high as u8,
175 self.alarm_low as u8,
176 self.resolution.config_byte(),
177 ];
178 self.bus
179 .write_bytes(&scratchpad)
180 .map_err(|error| DriverError::Bus(OneWireError::Pin(error)))?;
181 let readback = self.read_scratchpad()?;
182 if readback.resolution() != self.resolution {
183 return Err(DriverError::Sensor(crate::SensorError::Invalid));
184 }
185 Ok(())
186 }
187
188 pub fn measure(&mut self) -> Result<Scratchpad, DriverError<OneWireError<B::Error>>> {
204 self.select()?;
205 self.bus
206 .write_byte(command::CONVERT_T)
207 .map_err(|error| DriverError::Bus(OneWireError::Pin(error)))?;
208 self.delay.delay_us(self.resolution.max_conversion_micros());
209 self.read_scratchpad()
210 }
211
212 pub fn read_scratchpad(&mut self) -> Result<Scratchpad, DriverError<OneWireError<B::Error>>> {
223 self.select()?;
224 self.bus
225 .write_byte(command::READ_SCRATCHPAD)
226 .map_err(|error| DriverError::Bus(OneWireError::Pin(error)))?;
227 let mut bytes = [0u8; 9];
228 self.bus
229 .read_bytes(&mut bytes)
230 .map_err(|error| DriverError::Bus(OneWireError::Pin(error)))?;
231 Ok(Scratchpad::parse(&bytes)?)
232 }
233
234 pub fn save_settings(&mut self) -> Result<(), DriverError<OneWireError<B::Error>>> {
240 self.select()?;
241 self.bus
242 .write_byte(command::COPY_SCRATCHPAD)
243 .map_err(|error| DriverError::Bus(OneWireError::Pin(error)))?;
244 self.delay.delay_us(EEPROM_WRITE_MICROS);
245 Ok(())
246 }
247
248 pub fn powered_externally(&mut self) -> Result<bool, DriverError<OneWireError<B::Error>>> {
259 self.select()?;
260 self.bus
261 .write_byte(command::READ_POWER_SUPPLY)
262 .map_err(|error| DriverError::Bus(OneWireError::Pin(error)))?;
263 self.bus
264 .read_bit()
265 .map_err(|error| DriverError::Bus(OneWireError::Pin(error)))
266 }
267
268 fn select(&mut self) -> Result<(), DriverError<OneWireError<B::Error>>> {
269 match self.rom {
270 Some(rom) => self.bus.match_rom(&rom),
271 None => self.bus.skip_rom(),
272 }
273 .map_err(DriverError::Bus)
274 }
275}
276
277impl<B, D> Sensor for Ds18b20<B, D>
278where
279 B: OneWireBus + Send,
280 B::Error: core::fmt::Debug,
281 D: DelayNs + Send,
282{
283 type Reading = Scratchpad;
284
285 async fn read(&mut self) -> pamoja_core::Result<Scratchpad> {
286 self.measure().map_err(pamoja_core::Error::from)
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use crate::ds18b20::temperature_from_celsius;
294 use crate::SensorError;
295 use alloc::vec;
296 use alloc::vec::Vec;
297 use core::convert::Infallible;
298 use pamoja_hal::digital::PinState::{High, Low};
299 use pamoja_hal::onewire::{command as rom_command, BitBang};
300 use pamoja_hal::script::{block_on, DelayLog, PinScript};
301
302 struct Simulated {
305 rom: RomCode,
306 present: bool,
307 scratchpad: [u8; 9],
308 written: Vec<u8>,
309 reading: Vec<bool>,
310 external_power: bool,
311 }
312
313 impl Simulated {
314 fn new(celsius: f32) -> Simulated {
315 let raw = temperature_from_celsius(celsius, Resolution::Bits12);
316 Simulated {
317 rom: RomCode::new(0x28, 0x0000_05E2_FDC3).unwrap(),
318 present: true,
319 scratchpad: Scratchpad::new(raw, Resolution::Bits12, 125, -55).to_bytes(),
320 written: Vec::new(),
321 reading: Vec::new(),
322 external_power: true,
323 }
324 }
325
326 fn queue_bytes(&mut self, bytes: &[u8]) {
327 for &byte in bytes {
328 self.reading
329 .extend((0..8).map(|bit| (byte >> bit) & 1 == 1));
330 }
331 }
332
333 fn act(&mut self) {
334 let addressed = match self.written.first() {
335 Some(&rom_command::SKIP_ROM) => 1,
336 Some(&rom_command::MATCH_ROM)
337 if self.written.len() >= 9 && self.written[1..9] == self.rom.bytes() =>
338 {
339 9
340 }
341 _ => return,
342 };
343 match self.written.get(addressed) {
344 Some(&command::READ_SCRATCHPAD) => {
345 let scratchpad = self.scratchpad;
346 self.queue_bytes(&scratchpad);
347 }
348 Some(&command::WRITE_SCRATCHPAD) if self.written.len() == addressed + 4 => {
349 self.scratchpad[2] = self.written[addressed + 1];
350 self.scratchpad[3] = self.written[addressed + 2];
351 self.scratchpad[4] = self.written[addressed + 3];
352 self.scratchpad[8] = crate::ds18b20::crc8(&self.scratchpad[..8]);
353 }
354 Some(&command::READ_POWER_SUPPLY) => {
355 let external = self.external_power;
356 self.reading.push(external);
357 }
358 _ => {}
359 }
360 }
361 }
362
363 impl OneWireBus for Simulated {
364 type Error = Infallible;
365
366 fn reset(&mut self) -> Result<bool, Infallible> {
367 self.written.clear();
368 self.reading.clear();
369 Ok(self.present)
370 }
371
372 fn write_bit(&mut self, _bit: bool) -> Result<(), Infallible> {
373 unreachable!("bytes are written whole")
374 }
375
376 fn read_bit(&mut self) -> Result<bool, Infallible> {
377 Ok(if self.reading.is_empty() {
378 true
379 } else {
380 self.reading.remove(0)
381 })
382 }
383
384 fn write_byte(&mut self, byte: u8) -> Result<(), Infallible> {
385 self.written.push(byte);
386 self.act();
387 Ok(())
388 }
389 }
390
391 #[test]
392 fn measure_converts_then_reads_a_crc_checked_scratchpad() {
393 let mut probe = Ds18b20::new(Simulated::new(25.0625), DelayLog::new());
394 let scratchpad = probe.measure().unwrap();
395 assert_eq!(scratchpad.temperature_celsius(), 25.0625);
396 assert_eq!(scratchpad.resolution(), Resolution::Bits12);
397 let (bus, delay) = probe.release();
398 assert_eq!(
399 bus.written,
400 [rom_command::SKIP_ROM, command::READ_SCRATCHPAD]
401 );
402 assert_eq!(delay.waits_ns(), [750_000_000]);
403 }
404
405 #[test]
406 fn init_writes_the_settings_and_confirms_them_from_the_readback() {
407 let mut probe = Ds18b20::new(Simulated::new(20.0), DelayLog::new())
408 .with_resolution(Resolution::Bits9)
409 .with_alarms(30, -5);
410 probe.init().unwrap();
411 let scratchpad = probe.read_scratchpad().unwrap();
412 assert_eq!(scratchpad.resolution(), Resolution::Bits9);
413 assert_eq!(scratchpad.alarm_high(), 30);
414 assert_eq!(scratchpad.alarm_low(), -5);
415 probe.measure().unwrap();
416 let (_, delay) = probe.release();
417 assert_eq!(
418 delay.waits_ns(),
419 [93_750_000],
420 "a 9-bit conversion waits 93.75 ms"
421 );
422 }
423
424 #[test]
425 fn an_addressed_probe_matches_its_rom_and_a_stranger_is_ignored() {
426 let bus = Simulated::new(18.5);
427 let rom = bus.rom;
428 let mut probe = Ds18b20::addressed(bus, DelayLog::new(), rom);
429 assert_eq!(probe.rom(), Some(rom));
430 assert_eq!(probe.measure().unwrap().temperature_celsius(), 18.5);
431
432 let stranger = RomCode::new(0x28, 0x0000_0000_0001).unwrap();
433 let mut wrong = Ds18b20::addressed(Simulated::new(18.5), DelayLog::new(), stranger);
434 assert_eq!(wrong.measure(), Err(DriverError::Sensor(SensorError::Crc)));
435 }
436
437 #[test]
438 fn an_empty_bus_and_a_corrupted_scratchpad_are_reported_apart() {
439 let mut absent = Simulated::new(0.0);
440 absent.present = false;
441 let mut probe = Ds18b20::new(absent, DelayLog::new());
442 assert_eq!(
443 probe.measure(),
444 Err(DriverError::Bus(OneWireError::NoDevice))
445 );
446
447 let mut noisy = Simulated::new(0.0);
448 noisy.scratchpad[1] ^= 0x10;
449 let mut probe = Ds18b20::new(noisy, DelayLog::new());
450 assert_eq!(probe.measure(), Err(DriverError::Sensor(SensorError::Crc)));
451 }
452
453 #[test]
454 fn power_supply_and_eeprom_copy_follow_their_commands() {
455 let mut parasite = Simulated::new(0.0);
456 parasite.external_power = false;
457 let mut probe = Ds18b20::new(parasite, DelayLog::new());
458 assert!(!probe.powered_externally().unwrap());
459 probe.save_settings().unwrap();
460 let (bus, delay) = probe.release();
461 assert_eq!(
462 bus.written,
463 [rom_command::SKIP_ROM, command::COPY_SCRATCHPAD]
464 );
465 assert_eq!(delay.waits_ns(), [EEPROM_WRITE_MICROS * 1_000]);
466 }
467
468 #[test]
469 fn over_a_bit_banged_pin_the_wire_carries_the_datasheet_sequence() {
470 let raw = temperature_from_celsius(-10.125, Resolution::Bits12);
471 let bytes = Scratchpad::new(raw, Resolution::Bits12, 125, -55).to_bytes();
472 let mut levels = vec![Low, Low];
473 for byte in bytes {
474 levels.extend((0..8).map(|bit| if (byte >> bit) & 1 == 1 { High } else { Low }));
475 }
476 let bus = BitBang::new(PinScript::new(levels), DelayLog::new());
477 let mut probe = Ds18b20::new(bus, DelayLog::new());
478 let scratchpad = probe.measure().unwrap();
479 assert_eq!(scratchpad.temperature_celsius(), -10.125);
480 let (bus, _) = probe.release();
481 let (pin, _) = bus.into_parts();
482 assert_eq!(
483 pin.driven().len(),
484 2 + 16 + 16 + 2 + 16 + 16 + 144,
485 "two resets, four command bytes, and 72 read slots, two edges each"
486 );
487 assert_eq!(pin.remaining(), 0);
488 }
489
490 #[test]
491 fn as_a_sensor_it_reads_and_maps_to_celsius() {
492 let mut celsius = Ds18b20::new(Simulated::new(85.0), DelayLog::new())
493 .map(|scratchpad| scratchpad.temperature_celsius());
494 assert_eq!(block_on(celsius.read()).unwrap(), 85.0);
495 }
496}