diff --git a/README.rst b/README.rst index df10025..bde14b4 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Introduction .. image:: https://readthedocs.org/projects/adafruit-circuitpython-ds248x/badge/?version=latest - :target: https://docs.circuitpython.org/projects/ds248x/en/latest/ + :target: https://adafruit-circuitpython-ds248x.readthedocs.io/en/latest/ :alt: Documentation Status diff --git a/adafruit_ds248x.py b/adafruit_ds248x.py index f493433..9e01f15 100644 --- a/adafruit_ds248x.py +++ b/adafruit_ds248x.py @@ -15,6 +15,7 @@ **Hardware:** * `Adafruit DS2484 I2C to 1-Wire Bus Adapter Breakout `_ +* `Adafruit DS2482S-800 8 Channel I2C to 1-Wire Breakout `_ **Software and Dependencies:** @@ -46,6 +47,7 @@ _1WIRE_WRITE_BYTE = const(0xA5) _1WIRE_READ_BYTE = const(0x96) _TRIPLET = const(0x78) +_CHANNEL_SELECT = const(0xC3) # DS248x Register Definitions _REG_STATUS = const(0xF0) @@ -61,7 +63,7 @@ class Adafruit_DS248x: """ - Driver for the DS2484 1-Wire to I2C Bus Adapter. + Driver for the DS248x 1-Wire to I2C Bus Adapter. """ def __init__(self, i2c: I2C, address: int = 0x18): @@ -74,6 +76,7 @@ def __init__(self, i2c: I2C, address: int = 0x18): try: self.i2c_device = I2CDevice(i2c, address) self._address = address + self._selected_channel = -1 self.rom_no: bytearray = bytearray(8) self.last_discrepancy: int = 0 self.last_device_flag: bool = False @@ -87,7 +90,9 @@ def __init__(self, i2c: I2C, address: int = 0x18): raise RuntimeError("\tNo presence pulse") time.sleep(1) except RuntimeError as exception: - raise RuntimeError("DS248x initialization failed.") from exception + raise RuntimeError( + f"DS248x initialization failed: {exception}" + ) from exception def reset(self) -> bool: """ @@ -374,37 +379,82 @@ def branch_dir_taken(self) -> bool: status = self.status return status != 0xFF and (status & 0x80) - def ds18b20_temperature(self, rom: bytearray) -> float: + def ds18b20_temperature(self, rom: bytearray = None) -> float: """ - Reads the temperature from a DS18B20 sensor. + Reads the temperature from a DS18B20 sensor. If no ROM address is provided, + then a channel is read (0-7) from the DS2482S-800. - :param rom: The ROM address of the DS18B20 sensor + :param rom: The ROM address of the DS18B20 sensor (optional) :return: The temperature in Celsius """ - if rom[0] != _DS18B20_FAMILY: - raise ValueError("Device attached is not a DS18B20") - - self.onewire_reset() - self.onewire_byte = 0x55 # Match ROM command - for byte in rom: - self.onewire_byte = byte - - self.onewire_byte = 0x44 # Convert T command + if rom: + if rom[0] != _DS18B20_FAMILY: + raise ValueError("Device attached is not a DS18B20") + # Match ROM if a ROM address is provided + self.onewire_reset() + self.onewire_byte = 0x55 + for byte in rom: + self.onewire_byte = byte + else: + # Skip ROM if no ROM address is provided + self.onewire_reset() + self.onewire_byte = 0xCC + self.onewire_byte = 0x44 time.sleep(0.75) - - self.onewire_reset() - self.onewire_byte = 0x55 - for byte in rom: - self.onewire_byte = byte - self.onewire_byte = 0xBE # Read Scratchpad command - + if rom: + self.onewire_reset() + self.onewire_byte = 0x55 + for byte in rom: + self.onewire_byte = byte + else: + self.onewire_reset() + self.onewire_byte = 0xCC + self.onewire_byte = 0xBE data = bytearray(9) for i in range(9): data[i] = self.onewire_byte - raw = (data[1] << 8) | data[0] if raw & 0x8000: raw -= 1 << 16 celsius = raw / 16.0 - return celsius + + @property + def channel(self) -> int: + """ + Gets the current selected channel on the DS2482-800 by querying the device. + + :return: The currenctly selected channel. + """ + if self._selected_channel is None: + raise ValueError("No channel has been selected yet") + channel_code = self._selected_channel + (~self._selected_channel << 4) & 0xFF + cmd = bytearray([_CHANNEL_SELECT, channel_code]) + reply = bytearray(1) + with self.i2c_device as i2c: + i2c.write(cmd) + i2c.readinto(reply) + return_codes = [0xB8, 0xB1, 0xAA, 0xA3, 0x9C, 0x95, 0x8E, 0x87] + if reply[0] in return_codes: + return return_codes.index(reply[0]) + raise ValueError("Unknown channel code returned from the device") + + @channel.setter + def channel(self, chan: int) -> None: + """ + Sets the channel on the DS2482-800. + + :param chan: Channel to use, from 0 to 7 inclusive + """ + if chan > 7: + raise ValueError("Channel must be between 0 and 7") + channel_code = chan + (~chan << 4) & 0xFF + cmd = bytearray([_CHANNEL_SELECT, channel_code]) + reply = bytearray(1) + with self.i2c_device as i2c: + i2c.write(cmd) + i2c.readinto(reply) + return_codes = [0xB8, 0xB1, 0xAA, 0xA3, 0x9C, 0x95, 0x8E, 0x87] + if return_codes[chan] != reply[0]: + raise RuntimeError("Failed to set the channel") + self._selected_channel = chan diff --git a/docs/examples.rst b/docs/examples.rst index 8ce2466..41dcc74 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -6,3 +6,12 @@ Ensure your device works with this simple test. .. literalinclude:: ../examples/ds248x_simpletest.py :caption: examples/ds248x_simpletest.py :linenos: + +DS2482S-800 8-Channel example +----------------------------- + +Read all 8 channels from the DS2482S-800 + +.. literalinclude:: ../examples/ds2482s-800_8-channel_test.py + :caption: examples/ds2482s-800_8-channel_test.py + :linenos: diff --git a/examples/ds2482s-800_8-channel_test.py b/examples/ds2482s-800_8-channel_test.py new file mode 100644 index 0000000..a822cc0 --- /dev/null +++ b/examples/ds2482s-800_8-channel_test.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 Liz Clark for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +"""Adafruit DS2482S-800 8-Channel DS18B20 Example""" + +import time +import board +from adafruit_ds248x import Adafruit_DS248x + +# Initialize I2C bus and DS248x +i2c = board.STEMMA_I2C() +ds248x = Adafruit_DS248x(i2c) + +while True: + for i in range(8): + ds248x.channel = i + print(f"Reading channel {ds248x.channel}") + temperature = ds248x.ds18b20_temperature() + print(f"Temperature: {temperature:.2f} °C") + print() + time.sleep(1)