From 5686c510a105287297838f6ede4820977a8ee360 Mon Sep 17 00:00:00 2001 From: Drew McCalmont Date: Mon, 22 Jun 2026 23:30:43 -0400 Subject: [PATCH] feat(sensors): add 1-Wire (Dallas) temperature sensor support Add a portable 1-Wire bus driver to EnvironmentSensorManager covering the DallasTemperature-supported temperature families (DS18B20, DS18S20, DS1822, DS1825/MAX31850, DS28EA00). Each detected probe on the bus is registered as its own telemetry sub-channel, so multiple probes on one pin work without configuration. The driver gates on a 1-Wire presence pulse before engaging DallasTemperature, mirroring the I2C ACK check, so an absent bus pushes no telemetry entries. Boards opt in purely via build flags (ENV_INCLUDE_ONEWIRE + TELEM_ONEWIRE_PIN) with no source changes; the RAK4631 sensor env enables it on WB_IO1. Co-Authored-By: Claude Opus 4.8 (1M context) --- platformio.ini | 2 + .../sensors/EnvironmentSensorManager.cpp | 46 +++++++++++++ src/helpers/sensors/OneWireSensor.cpp | 68 +++++++++++++++++++ src/helpers/sensors/OneWireSensor.h | 50 ++++++++++++++ variants/rak4631/platformio.ini | 2 + 5 files changed, 168 insertions(+) create mode 100644 src/helpers/sensors/OneWireSensor.cpp create mode 100644 src/helpers/sensors/OneWireSensor.h diff --git a/platformio.ini b/platformio.ini index e16f7b8304..9998e4ba96 100644 --- a/platformio.ini +++ b/platformio.ini @@ -152,6 +152,8 @@ lib_deps = stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit BME680 Library @ ^2.0.4 adafruit/Adafruit BMP085 Library @ ^1.2.4 + paulstoffregen/OneWire @ ^2.3.8 + milesburton/DallasTemperature @ ^3.11.0 ; ----------------- TESTING --------------------- diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 73842d9eeb..8f1fbc64f7 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -160,6 +160,14 @@ static Adafruit_VL53L0X VL53L0X; static RAK12035_SoilMoisture RAK12035; #endif +#if ENV_INCLUDE_ONEWIRE +#ifndef TELEM_ONEWIRE_PIN +#error "ENV_INCLUDE_ONEWIRE requires TELEM_ONEWIRE_PIN to be defined in the board's build_flags" +#endif +#include "OneWireSensor.h" +static OneWireSensor ONEWIRE(TELEM_ONEWIRE_PIN); +#endif + #if ENV_INCLUDE_GPS && defined(RAK_BOARD) && !defined(RAK_WISMESH_TAG) #define RAK_WISBLOCK_GPS #endif @@ -475,6 +483,16 @@ static void query_rak12035(uint8_t ch, uint8_t sub_ch, CayenneLPP& lpp) { } #endif +#if ENV_INCLUDE_ONEWIRE +// 1-Wire has no I2C address and no init(wire, addr) entry — its +// presence-pulse probe and bus enumeration both live in +// OneWireSensor::begin(), called from the dedicated registration +// pass in EnvironmentSensorManager::begin(). +static void query_onewire(uint8_t ch, uint8_t sub_ch, CayenneLPP& lpp) { + ONEWIRE.query(ch, sub_ch, lpp); +} +#endif + #if ENV_INCLUDE_BME680_BSEC static void bsec_load_state() { using namespace Adafruit_LittleFS_Namespace; @@ -655,6 +673,34 @@ bool EnvironmentSensorManager::begin() { } } + // ---------------------------------------------------------- + // 1-Wire bus registration pass. + // + // Portability: any board enables 1-Wire by adding + // -D ENV_INCLUDE_ONEWIRE=1 + // -D TELEM_ONEWIRE_PIN= + // to its variant's build_flags. No source changes required. + // + // OneWireSensor::begin() issues a 1-Wire reset and checks for a + // presence pulse before touching DallasTemperature, mirroring the + // I2C ACK gate above. With no probe attached, it returns 0 and no + // entries are pushed — the same "never touch absent hardware" + // invariant applies here as on the I2C bus. + // ---------------------------------------------------------- + #if ENV_INCLUDE_ONEWIRE + { + uint8_t found = ONEWIRE.begin(); + if (found == 0) { + MESH_DEBUG_PRINTLN("1-Wire not detected on pin %d", (int)TELEM_ONEWIRE_PIN); + } else { + MESH_DEBUG_PRINTLN("Found %u 1-Wire temperature device(s) on pin %d", (unsigned)found, (int)TELEM_ONEWIRE_PIN); + for (uint8_t sub = 0; sub < found && _active_sensor_count < MAX_ACTIVE_SENSORS; sub++) { + _active_sensors[_active_sensor_count++] = { query_onewire, sub }; + } + } + } + #endif + return true; } diff --git a/src/helpers/sensors/OneWireSensor.cpp b/src/helpers/sensors/OneWireSensor.cpp new file mode 100644 index 0000000000..5b7988c3bf --- /dev/null +++ b/src/helpers/sensors/OneWireSensor.cpp @@ -0,0 +1,68 @@ +#include "OneWireSensor.h" + +#if ENV_INCLUDE_ONEWIRE + +// 1-Wire ROM family codes for the temperature devices the +// DallasTemperature library knows how to decode. Centralised here so +// the family filter in begin() reads as a list of supported parts. +static inline bool is_temperature_family(uint8_t family) { + switch (family) { + case 0x10: // DS18S20 / DS1820 + case 0x22: // DS1822 + case 0x28: // DS18B20 + case 0x3B: // DS1825 / MAX31850 + case 0x42: // DS28EA00 + return true; + default: + return false; + } +} + +OneWireSensor::OneWireSensor(uint8_t pin) + : _bus(pin), _dallas(&_bus) {} + +uint8_t OneWireSensor::begin() { + // Presence-pulse gate. OneWire::reset() returns 1 only when at + // least one device on the bus pulls the line low in response to + // the reset pulse. Without it, we skip DallasTemperature entirely. + if (_bus.reset() != 1) return 0; + + _dallas.begin(); + + const uint8_t bus_total = _dallas.getDeviceCount(); + _count = 0; + for (uint8_t i = 0; i < bus_total && _count < MAX_DEVICES; i++) { + DeviceAddress addr; + if (!_dallas.getAddress(addr, i)) continue; + + // Extension point: a future non-temperature family (e.g. DS2438 + // battery monitor, family 0x26) would dispatch on this family + // byte to its own driver and register a different query_* free + // function from the manager. The presence-pulse gate above and + // the per-device address storage below are family-agnostic and + // can be reused as-is. + if (!is_temperature_family(addr[0])) continue; + + memcpy(_addresses[_count], addr, sizeof(DeviceAddress)); + _count++; + } + + return _count; +} + +void OneWireSensor::query(uint8_t channel, uint8_t index, CayenneLPP& lpp) { + if (index >= _count) return; + + // Per-device blocking conversion keeps query() stateless and + // independent of call order — the cost is one conversion window + // (~750 ms at default 12-bit resolution) per device per cycle, + // which matches the synchronous read pattern used by the other + // sensors in EnvironmentSensorManager. + _dallas.requestTemperaturesByAddress(_addresses[index]); + const float t = _dallas.getTempC(_addresses[index]); + if (t == DEVICE_DISCONNECTED_C) return; + + lpp.addTemperature(channel, t); +} + +#endif // ENV_INCLUDE_ONEWIRE diff --git a/src/helpers/sensors/OneWireSensor.h b/src/helpers/sensors/OneWireSensor.h new file mode 100644 index 0000000000..b0ab47e8b2 --- /dev/null +++ b/src/helpers/sensors/OneWireSensor.h @@ -0,0 +1,50 @@ +#pragma once + +// Standalone 1-Wire bus driver for EnvironmentSensorManager. +// +// Covers every DallasTemperature-supported temperature family on one +// bus pin: DS18B20 (0x28), DS18S20 (0x10), DS1822 (0x22), DS1825 / +// MAX31850 (0x3B), DS28EA00 (0x42). Each detected device is exposed +// as its own telemetry sub-channel, so an air-and-water deployment +// with two probes on the same pin "just works". +// +// Portability contract: this file contains no board-specific pins or +// #ifdefs. The bus pin comes from the per-board build flag +// TELEM_ONEWIRE_PIN, and the whole module is compiled out when +// ENV_INCLUDE_ONEWIRE is not set. + +#if ENV_INCLUDE_ONEWIRE + +#include +#include +#include +#include + +class OneWireSensor { +public: + // Upper bound on devices enumerated from a single bus. Kept under + // EnvironmentSensorManager::MAX_ACTIVE_SENSORS so a fully populated + // bus cannot overflow the active-sensor table on its own. + static const uint8_t MAX_DEVICES = 8; + + explicit OneWireSensor(uint8_t pin); + + // Probes the bus for a 1-Wire presence pulse — the direct analog of + // the I2C ACK check the manager's address scan relies on. With no + // pulse, returns 0 and DallasTemperature is never engaged; this + // preserves the manager's "never touch absent hardware" invariant. + // Otherwise enumerates the bus and returns the number of detected + // temperature-family devices (clamped to MAX_DEVICES). + uint8_t begin(); + + // Reads device `index` and writes one temperature channel. + void query(uint8_t channel, uint8_t index, CayenneLPP& lpp); + +private: + OneWire _bus; + DallasTemperature _dallas; + uint8_t _count = 0; + DeviceAddress _addresses[MAX_DEVICES]; +}; + +#endif // ENV_INCLUDE_ONEWIRE diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 2bbba31463..5fe7d44895 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -186,6 +186,8 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' + -D ENV_INCLUDE_ONEWIRE=1 + -D TELEM_ONEWIRE_PIN=WB_IO1 ; -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter}