Browse Source

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) <[email protected]>
pull/2822/head
Drew McCalmont 1 month ago
parent
commit
5686c510a1
  1. 2
      platformio.ini
  2. 46
      src/helpers/sensors/EnvironmentSensorManager.cpp
  3. 68
      src/helpers/sensors/OneWireSensor.cpp
  4. 50
      src/helpers/sensors/OneWireSensor.h
  5. 2
      variants/rak4631/platformio.ini

2
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 ---------------------

46
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=<gpio>
// 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;
}

68
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

50
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 <Arduino.h>
#include <CayenneLPP.h>
#include <OneWire.h>
#include <DallasTemperature.h>
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

2
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}

Loading…
Cancel
Save