[ds248x] Add OneWireBus platform for DS248x I2C-to-1Wire bridges (#12717)
CI / Create common environment (push) Canceled after 0s
CI / Check pylint (push) Canceled after 0s
CI / Run script/ci-custom (push) Canceled after 0s
CI / Check import esphome.__main__ time (push) Canceled after 0s
CI / Test downstream esphome/device-builder (push) Canceled after 0s
CI / Run pytest (macOS-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (macOS-latest, 3.14) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.13) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.14) (push) Canceled after 0s
CI / Run pytest (windows-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (windows-latest, 3.14) (push) Canceled after 0s
CI / Determine which jobs to run (push) Canceled after 0s
CI / Run integration tests () (push) Canceled after 0s
CI / Run C++ unit tests (push) Canceled after 0s
CI / Run CodSpeed benchmarks (push) Canceled after 0s
CI / Run script/clang-tidy for LibreTiny (push) Canceled after 0s
CI / Run script/clang-tidy for ESP8266 (push) Canceled after 0s
CI / Run script/clang-tidy for RP2 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 Arduino (push) Canceled after 0s
CI / Run script/clang-tidy for ZEPHYR (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 1/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 2/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 3/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 C6 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 P4 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 S3 (push) Canceled after 0s
CI / Test components batch () (push) Canceled after 0s
CI / Test esp32 components with PlatformIO (push) Canceled after 0s
CI / Seed pre-commit cache (push) Canceled after 0s
CI / pre-commit.ci lite (push) Canceled after 0s
CI / Build target branch for memory impact (push) Canceled after 0s
CI / Build PR branch for memory impact (push) Canceled after 0s
CI / Comment memory impact (push) Canceled after 0s
CI / CI Status (push) Canceled after 0s

This commit is contained in:
Tom
2026-07-17 22:01:15 -07:00
committed by GitHub
parent a7dad14449
commit 52fe461e9c
12 changed files with 981 additions and 0 deletions
+1
View File
@@ -145,6 +145,7 @@ esphome/components/dlms_meter/* @latonita @PolarGoose @SimonFischer04 @Tomer27cz
esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee
esphome/components/ds2484/* @mrk-its
esphome/components/ds248x/* @tomwellnitz
esphome/components/dsmr/* @glmnet @PolarGoose
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M
+112
View File
@@ -0,0 +1,112 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_SLEEP_PIN, CONF_TYPE
CODEOWNERS = ["@tomwellnitz"]
MULTI_CONF = True
DEPENDENCIES = ["i2c"]
CONF_DS248X_ID = "ds248x_id"
CONF_BUS_SLEEP = "bus_sleep"
CONF_HUB_SLEEP = "hub_sleep"
CONF_ACTIVE_PULLUP = "active_pullup"
CONF_RESET_LOW_TIME = "reset_low_time"
CONF_MASTER_SAMPLE_TIME = "master_sample_time"
CONF_WRITE_0_LOW_TIME = "write_0_low_time"
CONF_RECOVERY_TIME = "recovery_time"
CONF_ACTIVE_PULLUP_RESISTANCE = "active_pullup_resistance"
TYPE_DS2482_100 = "ds2482-100"
TYPE_DS2482_101 = "ds2482-101"
TYPE_DS2482_800 = "ds2482-800"
TYPE_DS2484 = "ds2484"
CHANNEL_COUNTS = {
TYPE_DS2482_100: 1,
TYPE_DS2482_101: 1,
TYPE_DS2482_800: 8,
TYPE_DS2484: 1,
}
ds248x_ns = cg.esphome_ns.namespace("ds248x")
DS248xComponent = ds248x_ns.class_("DS248xComponent", cg.Component, i2c.I2CDevice)
def _component_schema(*extras):
schema = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DS248xComponent),
cv.Optional(CONF_ACTIVE_PULLUP, default=False): cv.boolean,
}
)
for extra in extras:
schema = schema.extend(extra)
return schema.extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x18))
SLEEP_SCHEMA = {
cv.Optional(CONF_SLEEP_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_BUS_SLEEP, default=False): cv.boolean,
cv.Optional(CONF_HUB_SLEEP, default=False): cv.boolean,
}
DS2484_SCHEMA = {
cv.Optional(CONF_RESET_LOW_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_MASTER_SAMPLE_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_WRITE_0_LOW_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_RECOVERY_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_ACTIVE_PULLUP_RESISTANCE): cv.enum(
{
# DS2484 Table 7: value codes 0-5 map to 500 ohm, 6-15 map to 1000 ohm.
"500ohm": 0,
"1000ohm": 6,
}
),
}
CONFIG_SCHEMA = cv.typed_schema(
{
TYPE_DS2482_100: _component_schema(),
TYPE_DS2482_101: _component_schema(SLEEP_SCHEMA),
TYPE_DS2482_800: _component_schema(),
TYPE_DS2484: _component_schema(SLEEP_SCHEMA, DS2484_SCHEMA),
},
key=CONF_TYPE,
lower=True,
)
def get_channel_count(config):
return CHANNEL_COUNTS[config[CONF_TYPE]]
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_active_pullup(config[CONF_ACTIVE_PULLUP]))
cg.add(var.set_channel_count(get_channel_count(config)))
if CONF_BUS_SLEEP in config:
cg.add(var.set_bus_sleep(config[CONF_BUS_SLEEP]))
if CONF_HUB_SLEEP in config:
cg.add(var.set_hub_sleep(config[CONF_HUB_SLEEP]))
if CONF_RESET_LOW_TIME in config:
cg.add(var.set_val_trstl(config[CONF_RESET_LOW_TIME]))
if CONF_MASTER_SAMPLE_TIME in config:
cg.add(var.set_val_tmsp(config[CONF_MASTER_SAMPLE_TIME]))
if CONF_WRITE_0_LOW_TIME in config:
cg.add(var.set_val_tw0l(config[CONF_WRITE_0_LOW_TIME]))
if CONF_RECOVERY_TIME in config:
cg.add(var.set_val_trec0(config[CONF_RECOVERY_TIME]))
if CONF_ACTIVE_PULLUP_RESISTANCE in config:
cg.add(var.set_val_rwpu(config[CONF_ACTIVE_PULLUP_RESISTANCE]))
if CONF_SLEEP_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
cg.add(var.set_sleep_pin(pin))
+320
View File
@@ -0,0 +1,320 @@
#include "ds248x.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome::ds248x {
static const char *const TAG = "ds248x";
void DS248xComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up DS248x...");
// Wake up device if sleep pin is configured
if (this->sleep_pin_) {
this->sleep_pin_->setup();
this->sleep_pin_->pin_mode(esphome::gpio::FLAG_OUTPUT);
this->sleep_pin_->digital_write(true); // Wake up
delay(1); // DS2482-101 Datasheet: tOSCWUP = 100μs (using 10x margin)
}
// Probe device
ESP_LOGD(TAG, "Probing DS248x...");
uint8_t status = 0;
if (this->read(&status, 1) == i2c::ERROR_OK) {
ESP_LOGD(TAG, "Device responded! Status: 0x%02x", status);
} else {
ESP_LOGW(TAG, "Device did not respond. Trying reset anyway...");
}
if (!this->device_reset_()) {
ESP_LOGW(TAG, "DS248x reset failed during setup!");
}
// Configure device
if (!this->device_configure_()) {
ESP_LOGE(TAG, "DS248x configuration failed!");
this->mark_failed();
return;
}
// Reset to Channel 0
this->select_channel(0);
ESP_LOGI(TAG, "DS248x initialized successfully.");
}
void DS248xComponent::on_shutdown() {
if (this->sleep_pin_ && (this->hub_sleep_ || this->bus_sleep_)) {
this->sleep_pin_->digital_write(false); // Sleep
}
}
void DS248xComponent::dump_config() {
ESP_LOGCONFIG(TAG, "DS248x:");
LOG_I2C_DEVICE(this);
ESP_LOGCONFIG(TAG, " Channel Count: %d", this->channel_count_);
ESP_LOGCONFIG(TAG, " Active Pullup: %s", YESNO(this->active_pullup_));
if (this->ds2484_mode_) {
ESP_LOGCONFIG(TAG, " DS2484 Mode: enabled");
}
}
// --- Internal Helpers ---
// Datasheet command durations are sub-2ms; allow a little margin before forcing recovery.
static constexpr uint32_t BUSY_TIMEOUT_MS = 5;
bool DS248xComponent::set_read_pointer_(uint8_t ptr) { return this->write_byte(DS248X_COMMAND_SETREADPTR, ptr); }
bool DS248xComponent::wait_busy_() {
uint32_t start = millis();
do {
uint8_t status;
if (this->read(&status, 1) == i2c::ERROR_OK && !(status & DS248X_STATUS_BUSY))
return true;
delayMicroseconds(100);
} while (millis() - start < BUSY_TIMEOUT_MS);
ESP_LOGW(TAG, "DS248x busy timeout");
bool recovered = this->device_reset_() && this->device_configure_();
this->current_channel_ = -1;
if (!recovered) {
ESP_LOGE(TAG, "DS248x recovery failed after busy timeout");
this->mark_failed();
}
return false;
}
bool DS248xComponent::device_reset_() {
ESP_LOGD(TAG, "Resetting device...");
uint8_t cmd = DS248X_COMMAND_RESET;
if (this->write(&cmd, 1) != i2c::ERROR_OK)
return false;
uint8_t status;
if (this->read(&status, 1) != i2c::ERROR_OK)
return false;
if (!(status & DS248X_STATUS_RST)) {
ESP_LOGW(TAG, "Device reset failed (RST bit not set)");
return false;
}
this->current_channel_ = -1;
return true;
}
bool DS248xComponent::device_configure_() {
ESP_LOGD(TAG, "Configuring device...");
if (!this->write_config_()) {
ESP_LOGW(TAG, "Config write/verify failed");
return false;
}
ESP_LOGD(TAG, "Configured successfully");
// DS2484 Configuration
if (this->ds2484_mode_) {
if (this->ds2484_trstl_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TRSTL, this->ds2484_trstl_))
return false;
if (this->ds2484_tmsp_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TMSP, this->ds2484_tmsp_))
return false;
if (this->ds2484_tw0l_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TW0L, this->ds2484_tw0l_))
return false;
if (this->ds2484_trec0_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TREC0, this->ds2484_trec0_))
return false;
if (this->ds2484_rwpu_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_RWPU, this->ds2484_rwpu_))
return false;
}
return true;
}
bool DS248xComponent::configure_ds2484_port_(uint8_t param, uint8_t val) {
uint8_t cmd = DS2484_COMMAND_ADJUSTPORT;
// Control Byte format (DS2484 Table 6): P[2:0] in bits 7:5, OD in bit 4, VAL[3:0] in bits 3:0
uint8_t data = ((param & 0x07) << 5) | (val & 0x0F);
// The DS2484 always acknowledges the Adjust 1-Wire Port control byte (datasheet "Adjust
// 1-Wire Port"), so a successful write confirms the update. We deliberately do not read
// back to verify: a single read of the Port Configuration register always returns the
// fixed 8-byte report starting at Byte 1 (tRSTL standard speed), not the parameter that
// was just written, so a per-parameter readback comparison would spuriously fail for
// tMSP/tW0L/tREC0/RWPU.
if (!this->write_byte(cmd, data)) {
ESP_LOGW(TAG, "DS2484 port config failed (param %d)", param);
return false;
}
return this->set_read_pointer_(DS248X_POINTER_STATUS);
}
bool DS248xComponent::write_config_() {
uint8_t config = 0;
if (this->active_pullup_)
config |= DS248X_CONFIG_ACTIVE_PULLUP;
// The DS248x only accepts the config byte if the upper nibble is the one's-complement of the lower nibble.
uint8_t config_byte = (config & 0x0F) | ((~config & 0x0F) << 4);
if (!this->write_byte(DS248X_COMMAND_WRITECONFIG, config_byte)) {
ESP_LOGW(TAG, "Failed to write config byte");
return false;
}
if (!this->set_read_pointer_(DS248X_POINTER_CONFIG)) {
return false;
}
uint8_t read_config;
if (this->read(&read_config, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Failed to read back config byte");
return false;
}
if ((read_config & 0x0F) != (config_byte & 0x0F)) {
ESP_LOGW(TAG, "Config mismatch! Wrote 0x%02x, Read 0x%02x", config_byte, read_config);
return false;
}
return this->set_read_pointer_(DS248X_POINTER_STATUS);
}
// --- Channel Selection ---
// Channel select codes: write code -> expected read code
static constexpr uint8_t CHANNEL_WRITE_CODES[8] = {0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87};
static constexpr uint8_t CHANNEL_READ_CODES[8] = {0xB8, 0xB1, 0xAA, 0xA3, 0x9C, 0x95, 0x8E, 0x87};
bool DS248xComponent::select_channel(uint8_t channel) {
if (this->channel_count_ <= 1)
return true;
if (channel >= this->channel_count_)
return false;
if (this->current_channel_ == channel)
return true;
if (!this->write_byte(DS248X_COMMAND_CHANNELSELECT, CHANNEL_WRITE_CODES[channel])) {
this->current_channel_ = -1;
return false;
}
uint8_t read_code;
if (this->read(&read_code, 1) != i2c::ERROR_OK) {
this->current_channel_ = -1;
return false;
}
if (read_code != CHANNEL_READ_CODES[channel]) {
ESP_LOGW(TAG, "Channel select failed! Expected 0x%02x, got 0x%02x", CHANNEL_READ_CODES[channel], read_code);
this->current_channel_ = -1;
return false;
}
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
this->current_channel_ = channel;
return true;
}
// --- 1-Wire Bus Operations ---
bool DS248xComponent::ow_reset(bool &presence) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
uint8_t cmd = DS248X_COMMAND_RESETWIRE;
if (this->write(&cmd, 1) != i2c::ERROR_OK)
return false;
if (!this->wait_busy_()) {
ESP_LOGW(TAG, "ow_reset: wait busy failed");
return false;
}
uint8_t status;
if (this->read(&status, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "ow_reset: read status failed");
return false;
}
if (status & DS248X_STATUS_SD) {
ESP_LOGW(TAG, "Short detected on 1-Wire bus!");
return false;
}
presence = (status & DS248X_STATUS_PPD);
return true;
}
bool DS248xComponent::ow_write_byte(uint8_t byte) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
if (!this->wait_busy_()) {
ESP_LOGW(TAG, "Device busy before writing byte 0x%02x", byte);
return false;
}
uint8_t cmd[2] = {DS248X_COMMAND_WRITEBYTE, byte};
if (this->write(cmd, 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "I2C write failed for byte 0x%02x", byte);
return false;
}
if (!this->wait_busy_()) {
ESP_LOGW(TAG, "Timeout waiting for write byte to complete!");
return false;
}
return true;
}
bool DS248xComponent::ow_read_byte(uint8_t &byte) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
uint8_t cmd = DS248X_COMMAND_READBYTE;
if (this->write(&cmd, 1) != i2c::ERROR_OK)
return false;
if (!this->wait_busy_())
return false;
if (!this->set_read_pointer_(DS248X_POINTER_DATA))
return false;
if (this->read(&byte, 1) != i2c::ERROR_OK)
return false;
return true;
}
bool DS248xComponent::search_triplet(bool search_direction, uint8_t &status) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
// DS248x Datasheet: 1-Wire Triplet command requires 2 bytes:
// Byte 1: Command code 0x78
// Byte 2: Direction byte (bit 7 = V, search direction if discrepancy)
uint8_t buffer[2] = {DS248X_COMMAND_TRIPLET, static_cast<uint8_t>(search_direction ? 0x80 : 0x00)};
if (this->write(buffer, 2) != i2c::ERROR_OK)
return false;
if (!this->wait_busy_())
return false;
if (this->read(&status, 1) != i2c::ERROR_OK)
return false;
return true;
}
} // namespace esphome::ds248x
+133
View File
@@ -0,0 +1,133 @@
#pragma once
// DS248x I2C-to-1-Wire Bridge Family
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2482-100.pdf
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2482-800.pdf
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2484.pdf
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome::ds248x {
// DS248x I2C Commands
static constexpr uint8_t DS248X_COMMAND_RESET = 0xF0;
static constexpr uint8_t DS248X_COMMAND_SETREADPTR = 0xE1;
static constexpr uint8_t DS248X_COMMAND_WRITECONFIG = 0xD2;
static constexpr uint8_t DS248X_COMMAND_CHANNELSELECT = 0xC3;
static constexpr uint8_t DS248X_COMMAND_RESETWIRE = 0xB4;
static constexpr uint8_t DS248X_COMMAND_WRITEBYTE = 0xA5;
static constexpr uint8_t DS248X_COMMAND_READBYTE = 0x96;
static constexpr uint8_t DS248X_COMMAND_TRIPLET = 0x78;
static constexpr uint8_t DS2484_COMMAND_ADJUSTPORT = 0xC3;
// DS2484 "Adjust 1-Wire Port" parameter codes (datasheet Table 6, control byte P[2:0])
static constexpr uint8_t DS2484_PORT_PARAM_TRSTL = 0x0;
static constexpr uint8_t DS2484_PORT_PARAM_TMSP = 0x1;
static constexpr uint8_t DS2484_PORT_PARAM_TW0L = 0x2;
static constexpr uint8_t DS2484_PORT_PARAM_TREC0 = 0x3;
static constexpr uint8_t DS2484_PORT_PARAM_RWPU = 0x4;
// DS248x Status Register Bits
static constexpr uint8_t DS248X_STATUS_BUSY = 0x01;
static constexpr uint8_t DS248X_STATUS_PPD = 0x02;
static constexpr uint8_t DS248X_STATUS_SD = 0x04;
static constexpr uint8_t DS248X_STATUS_RST = 0x10;
static constexpr uint8_t DS248X_STATUS_SBR = 0x20;
static constexpr uint8_t DS248X_STATUS_TSB = 0x40;
static constexpr uint8_t DS248X_STATUS_DIR = 0x80;
// DS248x Register Pointers
static constexpr uint8_t DS248X_POINTER_STATUS = 0xF0;
static constexpr uint8_t DS248X_POINTER_DATA = 0xE1;
static constexpr uint8_t DS248X_POINTER_CONFIG = 0xC3;
// DS248x Configuration Bits
static constexpr uint8_t DS248X_CONFIG_ACTIVE_PULLUP = 0x01;
/**
* @brief DS248x I2C-to-1-Wire Bridge Component.
*
* This component manages the DS248x chip (DS2482-100, DS2482-800, DS2484).
* It provides low-level 1-Wire bus operations via I2C.
*
* Usage: Configure DS248xOneWireBus instances for each channel.
* These buses implement the one_wire::OneWireBus interface for compatibility
* with all existing 1-Wire device components (dallas_temp, etc.).
*/
class DS248xComponent : public Component, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void on_shutdown() override;
float get_setup_priority() const override { return setup_priority::BUS; }
void set_sleep_pin(InternalGPIOPin *pin) { this->sleep_pin_ = pin; }
void set_bus_sleep(bool enabled) { this->bus_sleep_ = enabled; }
void set_hub_sleep(bool enabled) { this->hub_sleep_ = enabled; }
void set_channel_count(uint8_t count) { this->channel_count_ = count; }
void set_active_pullup(bool enabled) { this->active_pullup_ = enabled; }
// DS2484 Timing Parameters
void set_val_trstl(uint8_t val) {
this->ds2484_trstl_ = val;
this->ds2484_mode_ = true;
}
void set_val_tmsp(uint8_t val) {
this->ds2484_tmsp_ = val;
this->ds2484_mode_ = true;
}
void set_val_tw0l(uint8_t val) {
this->ds2484_tw0l_ = val;
this->ds2484_mode_ = true;
}
void set_val_trec0(uint8_t val) {
this->ds2484_trec0_ = val;
this->ds2484_mode_ = true;
}
void set_val_rwpu(uint8_t val) {
this->ds2484_rwpu_ = val;
this->ds2484_mode_ = true;
}
/// Get the channel count (1 for DS2482-100/DS2484, 8 for DS2482-800)
uint8_t get_channel_count() const { return this->channel_count_; }
// --- Core 1-Wire API (used by DS248xOneWireBus) ---
bool select_channel(uint8_t channel);
bool ow_reset(bool &presence);
bool ow_write_byte(uint8_t byte);
bool ow_read_byte(uint8_t &byte);
// --- Search support (used by DS248xOneWireBus) ---
bool search_triplet(bool search_direction, uint8_t &status);
protected:
InternalGPIOPin *sleep_pin_{nullptr};
uint8_t channel_count_ = 1;
bool bus_sleep_{false};
bool hub_sleep_{false};
bool active_pullup_ = false;
// DS2484 Config
bool ds2484_mode_ = false;
static constexpr uint8_t DS2484_PARAM_UNSET = 0xFF;
uint8_t ds2484_trstl_{DS2484_PARAM_UNSET};
uint8_t ds2484_tmsp_{DS2484_PARAM_UNSET};
uint8_t ds2484_tw0l_{DS2484_PARAM_UNSET};
uint8_t ds2484_trec0_{DS2484_PARAM_UNSET};
uint8_t ds2484_rwpu_{DS2484_PARAM_UNSET};
int8_t current_channel_{-1};
// Internal helpers
bool set_read_pointer_(uint8_t ptr);
bool wait_busy_();
bool device_reset_();
bool device_configure_();
bool configure_ds2484_port_(uint8_t param, uint8_t val);
bool write_config_();
};
} // namespace esphome::ds248x
@@ -0,0 +1,171 @@
#include "ds248x_one_wire_bus.h"
#include "ds248x.h"
#include "esphome/core/log.h"
namespace esphome::ds248x {
static const char *const TAG = "ds248x.one_wire";
void DS248xOneWireBus::setup() {
ESP_LOGCONFIG(TAG, "Setting up DS248x 1-Wire Bus (Channel %d)...", this->channel_);
// Parent setup happens in DS248xComponent::setup()
// We just need to scan for devices on this channel
if (!this->ensure_channel_()) {
ESP_LOGE(TAG, "Failed to select channel %d during setup", this->channel_);
this->mark_failed();
return;
}
// Perform device search on this channel
this->search();
ESP_LOGCONFIG(TAG, "Found %zu devices on channel %d", this->devices_.size(), this->channel_);
}
void DS248xOneWireBus::dump_config() {
ESP_LOGCONFIG(TAG, "DS248x 1-Wire Bus (Channel %d):", this->channel_);
this->dump_devices_(TAG);
}
bool DS248xOneWireBus::ensure_channel_() {
if (this->parent_ == nullptr) {
ESP_LOGE(TAG, "Parent not set!");
return false;
}
return this->parent_->select_channel(this->channel_);
}
int DS248xOneWireBus::reset_int() {
if (!this->ensure_channel_()) {
return -1;
}
bool presence = false;
if (!this->parent_->ow_reset(presence)) {
return -1;
}
return presence ? 1 : 0;
}
void DS248xOneWireBus::write8(uint8_t val) {
if (!this->ensure_channel_()) {
return;
}
if (!this->parent_->ow_write_byte(val)) {
ESP_LOGE(TAG, "Failed to write byte 0x%02X on channel %d", val, this->channel_);
}
}
void DS248xOneWireBus::write64(uint64_t val) {
if (!this->ensure_channel_()) {
return;
}
for (uint8_t i = 0; i < 8; i++) {
uint8_t byte = static_cast<uint8_t>(val >> (i * 8));
if (!this->parent_->ow_write_byte(byte)) {
ESP_LOGE(TAG, "Failed to write byte %d/8 (0x%02X) on channel %d - aborting write64", i + 1, byte, this->channel_);
return; // Stop writing to prevent sending corrupted data
}
}
}
uint8_t DS248xOneWireBus::read8() {
if (!this->ensure_channel_()) {
return 0;
}
uint8_t value = 0;
if (!this->parent_->ow_read_byte(value)) {
ESP_LOGE(TAG, "Failed to read byte on channel %d", this->channel_);
}
return value;
}
uint64_t DS248xOneWireBus::read64() {
if (!this->ensure_channel_()) {
return 0;
}
uint64_t value = 0;
for (uint8_t i = 0; i < 8; i++) {
uint8_t byte = 0;
if (!this->parent_->ow_read_byte(byte)) {
ESP_LOGE(TAG, "Failed to read byte %d/8 on channel %d - returning partial data", i + 1, this->channel_);
return value; // Return partial data to avoid blocking, caller should validate
}
value |= (static_cast<uint64_t>(byte) << (i * 8));
}
return value;
}
void DS248xOneWireBus::reset_search() {
this->search_last_discrepancy_ = 0;
this->search_last_device_flag_ = false;
this->search_address_ = 0;
}
uint64_t DS248xOneWireBus::search_int() {
if (!this->ensure_channel_()) {
return 0;
}
if (this->search_last_device_flag_) {
return 0;
}
uint8_t last_zero = 0;
uint64_t address = this->search_address_;
// Iterate through all 64 bits
for (uint8_t bit_number = 1; bit_number <= 64; bit_number++) {
uint64_t bit_mask = 1ULL << (bit_number - 1);
// Determine search direction
bool search_direction;
if (bit_number < this->search_last_discrepancy_) {
search_direction = (address & bit_mask) != 0;
} else {
search_direction = (bit_number == this->search_last_discrepancy_);
}
// Perform triplet operation
uint8_t status = 0;
if (!this->parent_->search_triplet(search_direction, status)) {
ESP_LOGW(TAG, "1-Wire triplet failed at bit %d on channel %d - aborting search", bit_number, this->channel_);
this->reset_search();
return 0;
}
bool id_bit = (status & DS248X_STATUS_SBR) != 0;
bool cmp_id_bit = (status & DS248X_STATUS_TSB) != 0;
bool dir_taken = (status & DS248X_STATUS_DIR) != 0;
if (id_bit && cmp_id_bit) {
// No devices participating
this->reset_search();
return 0;
}
if (!id_bit && !cmp_id_bit && !dir_taken) {
// Discrepancy, went 0 - record position
last_zero = bit_number;
}
// Update address based on direction taken
if (dir_taken) {
address |= bit_mask;
} else {
address &= ~bit_mask;
}
}
// Search successful
this->search_last_discrepancy_ = last_zero;
if (last_zero == 0) {
this->search_last_device_flag_ = true;
}
this->search_address_ = address;
return address;
}
} // namespace esphome::ds248x
@@ -0,0 +1,57 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/one_wire/one_wire_bus.h"
namespace esphome::ds248x {
class DS248xComponent;
/**
* @brief OneWireBus implementation for DS248x I2C-to-1-Wire bridges.
*
* This class wraps the DS248xComponent to provide the one_wire::OneWireBus interface,
* enabling compatibility with all existing 1-Wire device components (dallas_temp, etc.).
*
* For DS2482-800, multiple instances of this class can be created (one per channel).
* For DS2482-100/DS2484, a single instance is used.
*/
class DS248xOneWireBus : public one_wire::OneWireBus, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::BUS - 1.0f; }
/// Set the parent DS248x component
void set_parent(DS248xComponent *parent) { this->parent_ = parent; }
/// Set the 1-Wire channel (0-7, only relevant for DS2482-800)
void set_channel(uint8_t channel) { this->channel_ = channel; }
/// Get the channel number
uint8_t get_channel() const { return this->channel_; }
// OneWireBus interface implementation
int reset_int() override;
void write8(uint8_t val) override;
void write64(uint64_t val) override;
uint8_t read8() override;
uint64_t read64() override;
protected:
void reset_search() override;
uint64_t search_int() override;
/// Select the channel on the DS248x before any 1-Wire operation
bool ensure_channel_();
DS248xComponent *parent_{nullptr};
uint8_t channel_{0};
// Search state
uint64_t search_address_{0};
uint8_t search_last_discrepancy_{0};
bool search_last_device_flag_{false};
};
} // namespace esphome::ds248x
+56
View File
@@ -0,0 +1,56 @@
"""DS248x 1-Wire Bus Platform.
This platform creates one_wire bus instances backed by a DS248x I2C-to-1-Wire bridge.
It supports DS2482-100/101 (single channel), DS2482-800 (8 channels), and DS2484 (single channel).
For multi-channel devices (DS2482-800), create one platform entry per channel.
Each entry becomes a separate one_wire bus that can be used by dallas_temp and other 1-Wire devices.
"""
from esphome import final_validate as fv
import esphome.codegen as cg
from esphome.components.one_wire import OneWireBus
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_ID
from . import CONF_DS248X_ID, DS248xComponent, ds248x_ns, get_channel_count
CODEOWNERS = ["@tomwellnitz"]
DEPENDENCIES = ["ds248x"]
DS248xOneWireBus = ds248x_ns.class_("DS248xOneWireBus", OneWireBus, cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DS248xOneWireBus),
cv.GenerateID(CONF_DS248X_ID): cv.use_id(DS248xComponent),
cv.Optional(CONF_CHANNEL, default=0): cv.int_range(min=0, max=7),
}
).extend(cv.COMPONENT_SCHEMA)
def _final_validate(config):
"""Validate that the channel is within the parent's channel count."""
fconf = fv.full_config.get()
path = fconf.get_path_for_id(config[CONF_DS248X_ID])[:-1]
parent_config = fconf.get_config_for_path(path)
channel_count = get_channel_count(parent_config)
channel = config[CONF_CHANNEL]
if channel >= channel_count:
raise cv.Invalid(
f"Channel {channel} is invalid for DS248x with {channel_count} channel(s). "
f"Valid range: 0-{channel_count - 1}"
)
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
parent = await cg.get_variable(config[CONF_DS248X_ID])
cg.add(var.set_parent(parent))
cg.add(var.set_channel(config[CONF_CHANNEL]))
+115
View File
@@ -0,0 +1,115 @@
# Combined DS248x test covering all chip variants and options:
# - DS2482-100: active pullup, multiple sensors + index access
# - DS2482-101: sleep pin, bus_sleep / hub_sleep
# - DS2482-800: all 8 channels
# - DS2484: adjustable 1-Wire timing + RWPU pullup resistor selection
ds248x:
- id: ds2482_100
address: 0x18
type: ds2482-100
active_pullup: true
- id: ds2482_101
address: 0x19
type: ds2482-101
active_pullup: true
sleep_pin:
number: GPIO12
inverted: false
bus_sleep: true
hub_sleep: true
- id: ds2482_800
address: 0x1a
type: ds2482-800
active_pullup: true
- id: ds2484_hub
address: 0x1b
type: ds2484
active_pullup: true
# DS2484-specific 1-Wire timing parameters (optional fine-tuning)
reset_low_time: 8 # tRSTL: Reset low time
master_sample_time: 8 # tMSP: Master sample point
write_0_low_time: 8 # tW0L: Write-0 low time
recovery_time: 8 # tREC0: Recovery time
active_pullup_resistance: 1000ohm # RWPU: weak pullup resistor selection
one_wire:
- platform: ds248x
ds248x_id: ds2482_100
channel: 0
id: ow_100
- platform: ds248x
ds248x_id: ds2482_101
channel: 0
id: ow_101
- platform: ds248x
ds248x_id: ds2482_800
channel: 0
id: ow_800_0
- platform: ds248x
ds248x_id: ds2482_800
channel: 1
id: ow_800_1
- platform: ds248x
ds248x_id: ds2482_800
channel: 2
id: ow_800_2
- platform: ds248x
ds248x_id: ds2482_800
channel: 3
id: ow_800_3
- platform: ds248x
ds248x_id: ds2482_800
channel: 4
id: ow_800_4
- platform: ds248x
ds248x_id: ds2482_800
channel: 5
id: ow_800_5
- platform: ds248x
ds248x_id: ds2482_800
channel: 6
id: ow_800_6
- platform: ds248x
ds248x_id: ds2482_800
channel: 7
id: ow_800_7
- platform: ds248x
ds248x_id: ds2484_hub
channel: 0
id: ow_2484
sensor:
# DS2482-100: explicit address + index-based access on the same bus
- platform: dallas_temp
one_wire_id: ow_100
address: 0x1c0000031edd2a28
name: Temp 100 by address
resolution: 12
- platform: dallas_temp
one_wire_id: ow_100
index: 0
name: Temp 100 by index
# DS2482-101 (sleep variant)
- platform: dallas_temp
one_wire_id: ow_101
address: 0x578295491f64ff28
name: Temp 101
# DS2482-800: sensors on a few of the eight channels
- platform: dallas_temp
one_wire_id: ow_800_0
address: 0x1c0000031edd2a28
name: Temp 800 CH0
- platform: dallas_temp
one_wire_id: ow_800_3
index: 0
name: Temp 800 CH3 by index
- platform: dallas_temp
one_wire_id: ow_800_7
address: 0x2800000123456789
name: Temp 800 CH7
# DS2484 (adjustable timing)
- platform: dallas_temp
one_wire_id: ow_2484
address: 0x1c0000031edd2a28
name: Temp 2484
resolution: 12
@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-ard.yaml
<<: !include common.yaml
@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
<<: !include common.yaml
@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
<<: !include common.yaml
@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
<<: !include common.yaml