From 8c0ef44b0ba8e2a84dcc63ad2363a6a51879618a Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Mon, 3 Aug 2026 15:52:02 +0200 Subject: [PATCH] Add one wire i2c bridge support (#10078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * First version of DS248X bridge * Add first iteration of DS248X sensor * Supports single readings on DS2484 * Supports readings on ch0 for DS2484_800 * Detection of variant for DS248X * Minor fix on retries for sensor init * Allow multiple channel detect passes on 8-ch version * Always read temperature via ROM matching * Small comment to show how to send all channels * Minor logging changes * Prevent one-wire double definitions * Detect ROMs per round * Fix comment * Prevent skipping on DS2482 ALT3 check * Fix comment (again) * Fix style checks * Remove comment for multiple measurements * Address CodeRabbit review findings on DS248X sensor Set _variant on every detectVariant path and branch on the member, so a failed variant probe retries instead of falling into single-channel init. Search DS2482-800 channels into a scratch buffer so a transient one-wire failure cannot erase a ROM found on an earlier pass, and count channels that already hold a ROM. Check every one-wire return value in readTemperatureROM, validate the scratchpad CRC, and return DS248X_INVALID_TEMPERATURE on failure so the existing sentinel checks reject failed reads instead of reporting stale or uninitialised data. * Probe IIS2MDCTR WHO_AM_I before the DS248X status check At HMC5883L_ADDR the DS248X probe reads 0xF0. On the IIS2MDCTR that sub-address sets auto-increment and targets 0x70, which is reserved and returns an unspecified value; any of bits 0x02, 0x04 or 0x10 makes the probe claim the magnetometer as a DS2482. Reading the WHO_AM_I at 0x4F first is deterministic for both parts. A DS2482 does not acknowledge 0x4F, an invalid command code, and leaves its read pointer untouched, so the subsequent read returns Status, Configuration, Channel Selection or Read Data. None of those can hold 0x40 at scan time, so the DS248X probe still runs and detects it. This also restores develop's detection order and matches the structure already used at BMA423_ADDR: specific ID match, then probe, then the generic fallback. * Read only the reported channel in getMetrics getMetrics walked all eight DS2482-800 channels, but only channel 0 is ever written into the measurement. Each populated channel costs a blocking 750ms conversion inside readTemperatureROM, so a fully wired bridge stalled telemetry for roughly 6s per cycle and discarded seven of the eight readings. Channels without a sensor were already cheap thanks to the isValidROM guard, so this only affects boards that actually use more than one channel, which is the reason to fit a DS2482-800 in the first place. Multi-channel reporting is handled separately in #10192; a note records that it should start the conversion on every channel before waiting, rather than reading each channel end to end. * Trim DS248X comments to one line each --------- Co-authored-by: Thomas Göttgens --- platformio.ini | 2 + src/configuration.h | 8 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 95 +++++- .../Telemetry/EnvironmentTelemetry.cpp | 8 + src/modules/Telemetry/Sensor/DS248XSensor.cpp | 299 ++++++++++++++++++ src/modules/Telemetry/Sensor/DS248XSensor.h | 84 +++++ 7 files changed, 486 insertions(+), 11 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/DS248XSensor.cpp create mode 100644 src/modules/Telemetry/Sensor/DS248XSensor.h diff --git a/platformio.ini b/platformio.ini index 4b67f5303..aef78a858 100644 --- a/platformio.ini +++ b/platformio.ini @@ -233,6 +233,8 @@ lib_deps = closedcube/ClosedCube OPT3001@1.1.2 # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip + # renovate: datasource=github-tags depName=Adafruit DS248x packageName=adafruit/Adafruit_DS248x + https://github.com/adafruit/Adafruit_DS248x/archive/refs/tags/1.2.0.zip ; Environmental sensors with BSEC2 (Bosch proprietary IAQ) [environmental_extra] diff --git a/src/configuration.h b/src/configuration.h index ee5f55cbe..ccf4ba41f 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -295,6 +295,14 @@ along with this program. If not, see . #define LTR553ALS_ADDR 0x23 #define SEN5X_ADDR 0x69 #define SCD30_ADDR 0x61 +#define DS248X_ADDR 0x18 // same as MCP9808_ADDR, STK8BXX_ADDR and LIS3DH_ADDR +#define DS248X_ADDR_ALT1 0x19 // same as LIS3DH_ADDR_ALT and BMA423_ADDR +#define DS248X_ADDR_ALT2 0x1A // same as CST328_ADDR +#define DS248X_ADDR_ALT3 0x1B +#define DS248X_ADDR_ALT4 0x1C // same as QMC6310U_ADDR +#define DS248X_ADDR_ALT5 0x1D // same as DFROBOT_RAIN_ADDR +#define DS248X_ADDR_ALT6 0x1E // same as HMC5883L_ADDR +#define DS248X_ADDR_ALT7 0x1F // same as BBQ10_KB_ADDR // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 87965245a..491038878 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -103,6 +103,7 @@ class ScanI2C IIS2MDCTR, ISM330DHCX, SPA06, + DS248X, } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 3ad42ab04..5f04755e6 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -412,8 +412,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) type = TDECKKB; } break; - SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10", (uint8_t)addr.address); - + case BBQ10_KB_ADDR: + // Check status register (0xF0) for DS284X status and one-wire reset + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1); + if (registerValue & 0x16) { // One-wire reset after power-on + type = DS248X; + logFoundDevice("DS2482-800", (uint8_t)addr.address); + break; + } + type = BBQ10KB; + logFoundDevice("BB Q10", (uint8_t)addr.address); + break; SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address); #ifdef HAS_NCP5623 SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623", (uint8_t)addr.address); @@ -578,7 +587,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; } #endif - // Check register 0x07 for 0x0400 response to ID MCP9808 chip. registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x07), 2); if (registerValue == 0x0400) { @@ -592,6 +600,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) if (registerValue == 0x3300 || registerValue == 0x3333) { // RAK4631 WisBlock has LIS3DH register at 0x3333 type = LIS3DH; logFoundDevice("LIS3DH", (uint8_t)addr.address); + break; + } + + // Check status register (0xF0) for DS284X status and one-wire reset + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1); + if (registerValue & 0x16) { // One-wire reset after power-on + type = DS248X; + logFoundDevice("DS248X", (uint8_t)addr.address); } break; } @@ -636,7 +652,27 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) } break; SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address) - SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address) + case DS248X_ADDR_ALT3: + // Check status register (0xF0) for DS284X status and one-wire reset + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1); + if (registerValue & 0x16) { // One-wire reset after power-on + type = DS248X; + logFoundDevice("DS2482-800", (uint8_t)addr.address); + break; + } + break; + + case QMC6310U_ADDR: + // Check status register (0xF0) for DS284X status and one-wire reset + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1); + if (registerValue & 0x16) { // One-wire reset after power-on + type = DS248X; + logFoundDevice("DS2482-800", (uint8_t)addr.address); + break; + } + type = QMC6310U; + logFoundDevice("QMC6310U", (uint8_t)addr.address); + break; case QMI8658_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0A), 1); // get ID @@ -666,16 +702,25 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L", (uint8_t)addr.address) case HMC5883L_ADDR: + // WHO_AM_I first: the DS248X probe below reads a reserved address on the IIS2MDCTR registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x4FU), 1); // get ID if (registerValue == 0x40) { type = IIS2MDCTR; logFoundDevice("IIS2MDCTR", (uint8_t)addr.address); break; - } else { - type = HMC5883L; - logFoundDevice("HMC5883L", (uint8_t)addr.address); + } + + // Check status register (0xF0) for DS284X status and one-wire reset + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1); + if (registerValue & 0x16) { // One-wire reset after power-on + type = DS248X; + logFoundDevice("DS2482-800", (uint8_t)addr.address); break; } + + type = HMC5883L; + logFoundDevice("HMC5883L", (uint8_t)addr.address); + break; #ifdef HAS_QMA6100P SCAN_SIMPLE_CASE(QMA6100P_ADDR, QMA6100P, "QMA6100P", (uint8_t)addr.address) #else @@ -698,14 +743,25 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) if (registerValue == 0x3300 || registerValue == 0x3333) { // RAK4631 WisBlock has LIS3DH register at 0x3333 type = LIS3DH; logFoundDevice("LIS3DH", (uint8_t)addr.address); + break; } else if ((registerValue & 0xFF00) == 0x1100) { // Silan SC7A20: LIS3DH register map, but answers 0x11 here. type = SC7A20; logFoundDevice("SC7A20", (uint8_t)addr.address); - } else { - type = BMA423; - logFoundDevice("BMA423", (uint8_t)addr.address); + break; } + + // Check status register (0xF0) for DS284X status and one-wire reset + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1); + if (registerValue & 0x16) { // One-wire reset after power-on + type = DS248X; + logFoundDevice("DS2482-800", (uint8_t)addr.address); + break; + } + + type = BMA423; + logFoundDevice("BMA423", (uint8_t)addr.address); + break; case TCA9535_ADDR: case RAK120352_ADDR: @@ -753,11 +809,28 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address); SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address); SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address); - SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address); + case DFROBOT_RAIN_ADDR: + // Check status register (0xF0) for DS284X status and one-wire reset + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1); + if (registerValue & 0x16) { // One-wire reset after power-on + type = DS248X; + logFoundDevice("DS2482-800", (uint8_t)addr.address); + break; + } + type = DFROBOT_RAIN; + logFoundDevice("DFRobot Rain Gauge", (uint8_t)addr.address); + break; SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address); SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address); SCAN_SIMPLE_CASE(SCD30_ADDR, SCD30, "SCD30", (uint8_t)addr.address); case CST328_ADDR: + // Check status register (0xF0) for DS284X status and one-wire reset + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1); + if (registerValue & 0x16) { // One-wire reset after power-on + type = DS248X; + logFoundDevice("DS2482-800", (uint8_t)addr.address); + break; + } // Do we have the CST328 or the CST226SE,CST3530 { // T-Deck pro V1.1 new touch panel use CST3530 diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index b343b7b46..c2c819094 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -131,6 +131,10 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c #include "Sensor/BH1750Sensor.h" #endif +#if __has_include() +#include "Sensor/DS248XSensor.h" +#endif + #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -239,6 +243,10 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) // TODO Can we scan for multiple sensors connected on the same bus? addSensor(i2cScanner, ScanI2C::DeviceType::SHTXX); #endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::DS248X); +#endif + #endif } diff --git a/src/modules/Telemetry/Sensor/DS248XSensor.cpp b/src/modules/Telemetry/Sensor/DS248XSensor.cpp new file mode 100644 index 000000000..c98d2d0e3 --- /dev/null +++ b/src/modules/Telemetry/Sensor/DS248XSensor.cpp @@ -0,0 +1,299 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "DS248XSensor.h" +#include "TelemetrySensor.h" +#include + +// Dallas/Maxim CRC8 (reflected polynomial 0x8C), used to validate a DS18B20 scratchpad +static uint8_t ds18b20CRC8(const uint8_t *data, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) { + uint8_t inbyte = *data++; + for (uint8_t i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) { + crc ^= 0x8C; + } + inbyte >>= 1; + } + } + + return crc; +} + +DS248XSensor::DS248XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DS248X, "DS248X") {} + +ds248x_variant_t DS248XSensor::detectVariant() +{ + + // Wait until idle + if (!ds248x.busyWait(1000)) { + _variant = DS248X_UNKNOWN; + return _variant; + } + + // Try Channel Select command (only valid on DS2482-800) + if (!ds248x.selectChannel(0)) { + _variant = DS248X_DS2484; + } else { + _variant = DS248X_DS2482_800; + } + + return _variant; +} + +void DS248XSensor::printROM(const uint8_t *rom) +{ + LOG_INFO("%s: ROM found - %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", sensorName, rom[0], rom[1], rom[2], rom[3], rom[4], + rom[5], rom[6], rom[7]); +} + +bool DS248XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) +{ + _address = dev->address.address; + _bus = bus; + _port = dev->address.port; + LOG_INFO("Init sensor: %s", sensorName); + +#ifdef DS248X_I2C_CLOCK_SPEED + reClockI2C.setup(_bus, _port); + + LOG_INFO("%s: attempting to reclock speed to %uHz", sensorName, DS248X_I2C_CLOCK_SPEED); + reClockI2C.setClock(DS248X_I2C_CLOCK_SPEED); +#endif /* DS248X_I2C_CLOCK_SPEED */ + + if (!ds248x.begin(bus, _address)) { +#ifdef DS248X_I2C_CLOCK_SPEED + reClockI2C.restoreClock(); +#endif /* DS248X_I2C_CLOCK_SPEED */ + return false; + } + + // Try to init One-Wire with 3 retries. This detects ROMs consistently + // on the second one. + uint8_t numRetries = 3; + uint8_t rom[8]{}; + + for (uint8_t retry = 1; retry <= numRetries; retry++) { + bool initError = false; + uint8_t nROMDetected = 0; + + detectVariant(); + + if (_variant == DS248X_DS2482_800) { + + LOG_INFO("%s: Multi-channel DS2482-800 detected", sensorName); + + for (uint8_t channel = 0; channel < 8; channel++) { + + if (ds248x.selectChannel(channel)) { + + ds248x.OneWireReset(); + + // Scratch buffer: a failed pass must not erase a ROM found on an earlier one + uint8_t foundROM[8]{}; + if (ds248x.OneWireSearch(foundROM)) { + memcpy(ds2482800Data.ds248xData[channel].rom, foundROM, sizeof(foundROM)); + LOG_INFO("%s: One-wire rom detected on channel %u (%u/%u)", sensorName, channel, retry, numRetries); + printROM(ds2482800Data.ds248xData[channel].rom); + } else { + LOG_DEBUG("%s: no one-wire rom detected on channel %u (%u/%u)", sensorName, channel, retry, numRetries); + } + + } else { + LOG_WARN("%s: Failed to select channel %u", sensorName, channel); + } + + // Count every channel holding a ROM, including ones carried over from an earlier pass + if (isValidROM(ds2482800Data.ds248xData[channel].rom)) { + nROMDetected += 1; + } + } + + if (!nROMDetected) { + initError = true; + } + + } else if (_variant == DS248X_DS2484) { + LOG_INFO("%s: Single-channel DS2484 detected", sensorName); + + if (!ds248x.OneWireReset()) { + LOG_WARN("%s: One-wire reset unsuccessful (%u/%u)", sensorName, retry, numRetries); + initError = true; + } + + if (ds248x.shortDetected()) { + LOG_WARN("%s: One-wire short detected (%u/%u)", sensorName, retry, numRetries); + initError = true; + } + + if (!ds248x.presencePulseDetected()) { + LOG_WARN("%s: One-wire no presence pulse detected (%u/%u)", sensorName, retry, numRetries); + initError = true; + } + + // TODO - This will detect a ROM and will always read the same throughout runtime for the DS2484 + // If someone connects more than one one-wire temperature sensor, currently it will + // only read the first one (we only have one temperature to report) + if (!ds248x.OneWireSearch(ds248xData.rom)) { + LOG_WARN("%s: no one-wire rom detected (%u/%u)", sensorName, retry, numRetries); + initError = true; + } else { + LOG_INFO("%s: One-wire rom detected (%u/%u)", sensorName, retry, numRetries); + printROM(ds248xData.rom); + } + } else { + LOG_WARN("%s: Could not determine variant (%u/%u)", sensorName, retry, numRetries); + initError = true; + } + + if (initError && retry == numRetries) { +#ifdef DS248X_I2C_CLOCK_SPEED + reClockI2C.restoreClock(); +#endif /* DS248X_I2C_CLOCK_SPEED */ + LOG_ERROR("%s: Max retries for one-wire init (%u/%u). Aborting", sensorName, retry, numRetries); + return false; + } + + if (!initError) { + LOG_INFO("%s: Started one-wire (%u/%u)", sensorName, retry, numRetries); + status = true; + // We want to keep searching for ROMs on the DS248X_DS2482_800 + // and always do the three passes + if (_variant == ds248x_variant_t::DS248X_DS2484) { + break; + } + } + // TODO Potentially not needed, but taken from Adafruit's library example + delay(500); + } + +#ifdef DS248X_I2C_CLOCK_SPEED + LOG_INFO("%s: restoring clock speed", sensorName); + reClockI2C.restoreClock(); +#endif /* DS248X_I2C_CLOCK_SPEED */ + + initI2CSensor(); + return status; +} + +bool DS248XSensor::isValidROM(const uint8_t *rom) +{ + return (rom[0] || rom[1] || rom[2] || rom[3] || rom[4] || rom[5] || rom[6] || rom[7]); +} + +// Read a one-wire temperature sensor by matching it's ROM +float DS248XSensor::readTemperatureROM(const uint8_t *rom) +{ +#ifdef DS248X_I2C_CLOCK_SPEED + LOG_DEBUG("%s: attempting to reclock speed to %uHz", sensorName, DS248X_I2C_CLOCK_SPEED); + reClockI2C.setClock(DS248X_I2C_CLOCK_SPEED); +#endif /* DS248X_I2C_CLOCK_SPEED */ + + uint8_t data[9]{}; + + // Select the DS18B20 device + bool ok = ds248x.OneWireReset() && ds248x.OneWireWriteByte(DS18B20_CMD_MATCH_ROM); // Match ROM command + for (int i = 0; ok && i < 8; i++) { + ok = ds248x.OneWireWriteByte(rom[i]); + } + + // Start temperature conversion + ok = ok && ds248x.OneWireWriteByte(DS18B20_CMD_CONVERT_T); // Convert T command + + if (ok) { + delay(750); // Wait for conversion (750ms for maximum precision) + + // Read scratchpad + ok = ds248x.OneWireReset() && ds248x.OneWireWriteByte(DS18B20_CMD_MATCH_ROM); // Match ROM command + for (int i = 0; ok && i < 8; i++) { + ok = ds248x.OneWireWriteByte(rom[i]); + } + ok = ok && ds248x.OneWireWriteByte(DS18B20_CMD_READ_SCRATCHPAD); // Read Scratchpad command + + for (int i = 0; ok && i < 9; i++) { + ok = ds248x.OneWireReadByte(&data[i]); + } + } + +#ifdef DS248X_I2C_CLOCK_SPEED + LOG_DEBUG("%s: restoring clock speed", sensorName); + reClockI2C.restoreClock(); +#endif /* DS248X_I2C_CLOCK_SPEED */ + + if (!ok) { + LOG_WARN("%s: One-wire transaction failed", sensorName); + return DS248X_INVALID_TEMPERATURE; + } + + // The scratchpad ends with a CRC8 over its first eight bytes + if (ds18b20CRC8(data, 8) != data[8]) { + LOG_WARN("%s: Scratchpad CRC mismatch", sensorName); + return DS248X_INVALID_TEMPERATURE; + } + + // Calculate temperature + int16_t raw = (data[1] << 8) | data[0]; + float celsius = (float)raw / 16.0; + + return celsius; +} + +bool DS248XSensor::readTemperatureChannel(uint8_t channel) +{ + if (!isValidROM(ds2482800Data.ds248xData[channel].rom)) { + LOG_DEBUG("%s: No ROM in channel %u", sensorName, channel); + return false; + } + // Select the channel on the DS2482-800 + if (!ds248x.selectChannel(channel)) { + // Handle error if channel selection fails + LOG_WARN("%s: Failed to select channel %u", sensorName, channel); + return false; + } + + float temperature; + temperature = readTemperatureROM(ds2482800Data.ds248xData[channel].rom); + + if (temperature == DS248X_INVALID_TEMPERATURE) { + LOG_WARN("%s: Failed to read temperature in channel %u", sensorName, channel); + return false; + } + + ds2482800Data.ds248xData[channel].temperature = temperature; + LOG_DEBUG("%s: read temperature in channel %u: %0.2f", sensorName, channel, temperature); + return true; +} + +bool DS248XSensor::getMetrics(meshtastic_Telemetry *measurement) +{ + if (_variant == ds248x_variant_t::DS248X_DS2484) { + float temperature = readTemperatureROM(ds248xData.rom); + if (temperature != DS248X_INVALID_TEMPERATURE) { + measurement->variant.environment_metrics.temperature = temperature; + measurement->variant.environment_metrics.has_temperature = true; + LOG_DEBUG("Got %s readings: temperature=%.2f", sensorName, measurement->variant.environment_metrics.temperature); + return true; + } + } else if (_variant == ds248x_variant_t::DS248X_DS2482_800) { + // Only ch0 is reported, and each populated channel blocks 750ms on its conversion + // TODO Support more than one temperature via repeated (3.0) + // TODO Select which channel can be reported as main temperature + if (readTemperatureChannel(0)) { + measurement->variant.environment_metrics.temperature = ds2482800Data.ds248xData[0].temperature; + measurement->variant.environment_metrics.has_temperature = true; + LOG_DEBUG("Got %s readings: temperature=%.2f", sensorName, measurement->variant.environment_metrics.temperature); + return true; + } + return false; + } + return false; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/DS248XSensor.h b/src/modules/Telemetry/Sensor/DS248XSensor.h new file mode 100644 index 000000000..bfe18a035 --- /dev/null +++ b/src/modules/Telemetry/Sensor/DS248XSensor.h @@ -0,0 +1,84 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../detect/ReClockI2C.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +#ifndef DS248X_I2C_CLOCK_SPEED +#define DS248X_I2C_CLOCK_SPEED 400000 +#endif + +#ifndef DS18B20_CMD_SKIP_ROM +#define DS18B20_CMD_SKIP_ROM 0xCC +#endif + +#ifndef DS18B20_CMD_CONVERT_T +#define DS18B20_CMD_CONVERT_T 0x44 +#endif + +#ifndef DS18B20_CMD_READ_SCRATCHPAD +#define DS18B20_CMD_READ_SCRATCHPAD 0xBE +#endif + +#ifndef DS18B20_FAMILY_CODE +#define DS18B20_FAMILY_CODE 0x28 +#endif + +#ifndef DS18B20_CMD_MATCH_ROM +#define DS18B20_CMD_MATCH_ROM 0x55 +#endif + +#ifndef DS248X_CMD_CHANNEL_SELECT +#define DS248X_CMD_CHANNEL_SELECT 0xC3 +#endif + +#ifndef DS248X_REG_CHANNEL +#define DS248X_REG_CHANNEL 0xD2 +#endif + +#ifndef DS248X_CH0 +#define DS248X_CH0 0xF0 +#endif + +// Returned by readTemperatureROM when the transaction fails or the scratchpad CRC mismatches +#ifndef DS248X_INVALID_TEMPERATURE +#define DS248X_INVALID_TEMPERATURE -1000.0f +#endif + +typedef enum { DS248X_UNKNOWN = 0, DS248X_DS2484, DS248X_DS2482_800 } ds248x_variant_t; + +struct _DS248XData { + uint8_t rom[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + float temperature; +}; + +struct _DS2482800Data { + _DS248XData ds248xData[8]; +}; + +class DS248XSensor : public TelemetrySensor +{ + private: + Adafruit_DS248x ds248x; + ds248x_variant_t _variant = DS248X_UNKNOWN; + _DS248XData ds248xData{}; + _DS2482800Data ds2482800Data{}; +#ifdef DS248X_I2C_CLOCK_SPEED + ReClockI2C reClockI2C; +#endif + void printROM(const uint8_t *rom); + bool isValidROM(const uint8_t *rom); + float readTemperatureROM(const uint8_t *rom); + bool readTemperatureChannel(uint8_t channel); + + public: + DS248XSensor(); + ds248x_variant_t detectVariant(); + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; +}; + +#endif \ No newline at end of file