From efcefe6627197b5c64b55210bd974586f49d382f Mon Sep 17 00:00:00 2001 From: Thierry DUVERNOY Date: Wed, 15 Jan 2025 15:31:35 +0100 Subject: [PATCH] Initialize Dallas PIO component to manage 1-wire Dallas addressable switches as DS2413, DS2406, DS2408 --- esphome/components/dallas_pio/__init__.py | 8 + .../components/dallas_pio/binary_sensor.cpp | 84 ++++ esphome/components/dallas_pio/binary_sensor.h | 42 ++ .../components/dallas_pio/binary_sensor.py | 68 +++ esphome/components/dallas_pio/dallas_pio.cpp | 406 ++++++++++++++++++ esphome/components/dallas_pio/dallas_pio.h | 96 +++++ esphome/components/dallas_pio/dallas_pio.py | 62 +++ .../dallas_pio/dallas_pio_constants.h | 17 + esphome/components/dallas_pio/switch.cpp | 81 ++++ esphome/components/dallas_pio/switch.h | 45 ++ esphome/components/dallas_pio/switch.py | 68 +++ 11 files changed, 977 insertions(+) create mode 100644 esphome/components/dallas_pio/__init__.py create mode 100644 esphome/components/dallas_pio/binary_sensor.cpp create mode 100644 esphome/components/dallas_pio/binary_sensor.h create mode 100644 esphome/components/dallas_pio/binary_sensor.py create mode 100644 esphome/components/dallas_pio/dallas_pio.cpp create mode 100644 esphome/components/dallas_pio/dallas_pio.h create mode 100644 esphome/components/dallas_pio/dallas_pio.py create mode 100644 esphome/components/dallas_pio/dallas_pio_constants.h create mode 100644 esphome/components/dallas_pio/switch.cpp create mode 100644 esphome/components/dallas_pio/switch.h create mode 100644 esphome/components/dallas_pio/switch.py diff --git a/esphome/components/dallas_pio/__init__.py b/esphome/components/dallas_pio/__init__.py new file mode 100644 index 0000000000..875455662e --- /dev/null +++ b/esphome/components/dallas_pio/__init__.py @@ -0,0 +1,8 @@ +from .dallas_pio import CONFIG_SCHEMA, DallasPio, to_code + +CODEOWNERS = ["@tdy91"] + +DEPENDENCIES = ["one_wire", "binary_sensor", "switch"] +AUTO_LOAD = ["binary_sensor", "switch"] + +__all__ = ["CONFIG_SCHEMA", "DallasPio", "to_code"] diff --git a/esphome/components/dallas_pio/binary_sensor.cpp b/esphome/components/dallas_pio/binary_sensor.cpp new file mode 100644 index 0000000000..71b686cf07 --- /dev/null +++ b/esphome/components/dallas_pio/binary_sensor.cpp @@ -0,0 +1,84 @@ +#include "binary_sensor.h" + +namespace esphome { +namespace dallas_pio { + +void DallasPioBinarySensor::setup() { + if (this->dallas_pio_ == nullptr) { + ESP_LOGE(TAG, "DallasPioBinarySensor setup failed - DallasPio not set for this binary sensor."); + return; + } + ESP_LOGI(TAG, "DallasPioBinarySensor setup - DallasPio set for this binary sensor."); + + this->address_ = this->dallas_pio_->get_address(); + this->reference_ = this->dallas_pio_->get_reference(); + this->family_code_ = this->dallas_pio_->get_family_code(); + // this->bus_ = this->dallas_pio_->get_bus(); +} + +void DallasPioBinarySensor::dump_config() { + ESP_LOGCONFIG(TAG, "Dallas PIO Binary Sensor:"); + ESP_LOGCONFIG(TAG, " dallas_pio_id: %s", this->dallas_pio_->get_id().c_str()); + + if (this->reference_ == "DS2408") { + ESP_LOGCONFIG(TAG, " pin: P%c", '0' - 1 + this->pin_); // P0, P1, ... + } else { + ESP_LOGCONFIG(TAG, " pin: PIO%c", 'A' - 1 + this->pin_); // PIOA, PIOB, ... + } + ESP_LOGCONFIG(TAG, " pin inverted: %s", this->pin_inverted_ ? "true" : "false"); + /* + if (this->address_ == 0) { + ESP_LOGCONFIG(TAG, " address: invalid (null)"); + } else { + ESP_LOGCONFIG(TAG, " address: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", + (uint8_t) (this->address_ >> 56), + (uint8_t) (this->address_ >> 48), + (uint8_t) (this->address_ >> 40), + (uint8_t) (this->address_ >> 32), + (uint8_t) (this->address_ >> 24), + (uint8_t) (this->address_ >> 16), + (uint8_t) (this->address_ >> 8), + (uint8_t) (this->address_)); + ESP_LOGCONFIG(TAG, " Family code: 0x%02X", this->family_code_); + ESP_LOGCONFIG(TAG, " Reference: %s", this->reference_.c_str()); + } + */ + ESP_LOGCONFIG(TAG, " Name: %s", this->EntityBase::name_.c_str()); + + // this->component_->log_one_wire_device(); + LOG_UPDATE_INTERVAL(this); +} + +void DallasPioBinarySensor::update() { + // Ref Analog Devices DS2413.pdf p6 of 19 + // | b7 b6 b5 b4 | b3 | b2 | b1 | b0 | + // | Complement of b3 to b0 | PIOB Output | PIOB Pin | PIOA Output | PIOA Pin | + // | | Latch State | State | Latch State | State | + // ESP_LOGCONFIG(TAG, "Dallas PIO Binary Sensor Update !"); + uint8_t state = 0; + if (this->dallas_pio_->check_address() == 0) { + this->status_set_warning(); + return; + } + this->status_clear_warning(); + + if (this->reference_ == "DS2413") { + if (!this->dallas_pio_->read_state(state, this->pin_)) { + return; + } + } else if (this->reference_ == "DS2406") { + // Action pour DS2406 + } else if (this->reference_ == "DS2408") { + // Action pour DS2408 + } else { + // Action par défaut + } + + if (this->pin_inverted_) { + state = !state; + } + this->publish_state(state); +} + +} // namespace dallas_pio +} // namespace esphome diff --git a/esphome/components/dallas_pio/binary_sensor.h b/esphome/components/dallas_pio/binary_sensor.h new file mode 100644 index 0000000000..b732a6bb68 --- /dev/null +++ b/esphome/components/dallas_pio/binary_sensor.h @@ -0,0 +1,42 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "dallas_pio_constants.h" +#include "dallas_pio.h" + +namespace esphome { +namespace dallas_pio { + +class DallasPioBinarySensor : public PollingComponent, public binary_sensor::BinarySensor { + public: + DallasPioBinarySensor() : dallas_pio_(nullptr), pio_pin_(0) {} + DallasPioBinarySensor(DallasPio *dallas_pio, uint8_t pio_pin) : dallas_pio_(dallas_pio), pio_pin_(pio_pin) {} + void setup() override; + void dump_config() override; + void update() override; + using EntityBase::set_name; + void set_dallas_pio(DallasPio *dallas_pio) { this->dallas_pio_ = dallas_pio; } + void set_address(uint64_t address) { this->address_ = address; } + void set_pin(uint8_t pin) { this->pin_ = pin; } + void set_pin_inverted(bool pin_inverted) { this->pin_inverted_ = pin_inverted; } + void set_pin_mode(bool input, bool output) { + this->is_input_ = input; + this->is_output_ = output; + } + // void set_inverted(bool inverted) { this->inverted_ = inverted; } + + protected: + DallasPio *dallas_pio_; + uint8_t pio_pin_; + std::string reference_; + uint8_t family_code_; + uint64_t address_; + uint8_t pin_; + bool pin_inverted_; + bool is_input_; + bool is_output_; +}; + +} // namespace dallas_pio +} // namespace esphome diff --git a/esphome/components/dallas_pio/binary_sensor.py b/esphome/components/dallas_pio/binary_sensor.py new file mode 100644 index 0000000000..9105655890 --- /dev/null +++ b/esphome/components/dallas_pio/binary_sensor.py @@ -0,0 +1,68 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import CONF_INVERTED, CONF_MODE, CONF_PIN + +DEPENDENCIES = ["binary_sensor", "dallas_pio"] +AUTO_LOAD = ["binary_sensor"] + +dallas_pio_ns = cg.esphome_ns.namespace("dallas_pio") +DallasPioBinarySensor = dallas_pio_ns.class_( + "DallasPioBinarySensor", + cg.PollingComponent, + binary_sensor.BinarySensor, +) + + +def validate_mode(value): + if value.get("input", False) is not True: + raise cv.Invalid( + "The 'input' field must always be 'true' for Dallas PIO binary sensors." + ) + if value.get("output", False) is True: + raise cv.Invalid("The 'output' field must be 'false' if 'input' is 'true'.") + return value + + +CUSTOM_PIN_SCHEMA = cv.Schema( + { + cv.Required("number"): cv.one_of("PIOA", "PIOB", upper=True), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.Optional(CONF_MODE, default={"input": True}): validate_mode, + } +) + +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema( + DallasPioBinarySensor, + ) + .extend( + { + cv.Required(CONF_PIN): CUSTOM_PIN_SCHEMA, + cv.Required("dallas_pio_id"): cv.use_id(dallas_pio_ns.class_("DallasPio")), + } + ) + .extend(cv.polling_component_schema("1s")) +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + + dallas_pio_ref = await cg.get_variable(config["dallas_pio_id"]) + cg.add(var.set_dallas_pio(dallas_pio_ref)) + + # Extract pin configuration + pin_config = config[CONF_PIN] + pin_number = pin_config["number"] # "PIOA" or "PIOB" + pin_inverted = pin_config[CONF_INVERTED] + pin_mode = pin_config[CONF_MODE] + # C++ configuration association + if pin_number == "PIOA": + cg.add(var.set_pin(0x01)) # PIOA assiociated to 0x01 + elif pin_number == "PIOB": + cg.add(var.set_pin(0x02)) # PIOB assiociated to 0x02 + cg.add(var.set_pin_inverted(pin_inverted)) + cg.add(var.set_pin_mode(pin_mode.get("input", True), pin_mode.get("output", False))) + # cg.add(var.set_inverted(inverted)) diff --git a/esphome/components/dallas_pio/dallas_pio.cpp b/esphome/components/dallas_pio/dallas_pio.cpp new file mode 100644 index 0000000000..3840a0a4a3 --- /dev/null +++ b/esphome/components/dallas_pio/dallas_pio.cpp @@ -0,0 +1,406 @@ +#include "dallas_pio.h" + +namespace esphome { +namespace dallas_pio { + +DallasPio::DallasPio() + : id_(""), + one_wire_id_(), + name_(""), + reference_(""), + address_(0), + reference_from_address_(""), + family_code_(0), + is_setup_done_(false) {} + +void DallasPio::set_id(const std::string &id) { this->id_ = id; } + +void DallasPio::set_name(const std::string &name) { this->name_ = name; } + +void DallasPio::set_address(uint64_t address) { + this->address_ = address; + OneWireDevice::set_address(address); +} + +void DallasPio::set_reference(const std::string &reference) { this->reference_ = reference; } + +void DallasPio::set_crc_enabled(bool enabled) { this->crc_enabled_ = enabled; } + +void DallasPio::set_one_wire_id(const std::string &one_wire_id) { this->one_wire_id_ = one_wire_id; } + +bool DallasPio::check_address() { return this->check_address_(); } + +void DallasPio::setup() { + if (this->is_setup_done_) + return; + // ESP_LOGD("dallas_pio", "Setting up DallasPio: %s", this->name_.c_str()); + this->initialize_reference_(); + this->is_setup_done_ = true; +} + +void DallasPio::dump_config() { + ESP_LOGCONFIG(TAG, "Dallas PIO :"); + ESP_LOGCONFIG(TAG, " Id: %s", this->id_.c_str()); + ESP_LOGCONFIG(TAG, " Reference: %s", this->reference_.c_str()); + if (this->address_ == 0) { + ESP_LOGCONFIG(TAG, " address: invalid (null)"); + } else { + ESP_LOGCONFIG(TAG, " address: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", (uint8_t) (this->address_ >> 56), + (uint8_t) (this->address_ >> 48), (uint8_t) (this->address_ >> 40), (uint8_t) (this->address_ >> 32), + (uint8_t) (this->address_ >> 24), (uint8_t) (this->address_ >> 16), (uint8_t) (this->address_ >> 8), + (uint8_t) (this->address_)); + ESP_LOGCONFIG(TAG, " Family code: 0x%02X", this->family_code_); + if (reference_ != reference_from_address_) { + ESP_LOGCONFIG(TAG, " Reference from family code: %s", this->reference_from_address_.c_str()); + ESP_LOGW(TAG, " WARNING: reference from family code does not match reference !!!"); + } + } + ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str()); + if (this->one_wire_id_.has_value()) { + ESP_LOGCONFIG(TAG, " OneWire ID: %s", this->one_wire_id_->c_str()); + } else { + ESP_LOGCONFIG(TAG, " OneWire ID: not defined"); + } + LOG_ONE_WIRE_DEVICE(this); +} + +bool DallasPio::read_state(uint8_t &state, uint8_t pin) { + this->pin_ = pin; + if (this->reference_ == "DS2413") { + // ESP_LOGD(TAG, "DallasPio read_state %u pin %u", state, this->pin_); + return this->ds2413_get_state_(state); + } else if (this->reference_ == "DS2406") { + return this->ds2406_get_state_(state, this->crc_enabled_); + } else if (this->reference_ == "DS2408") { + // Action pour DS2408 + } else { + // Action par défaut + } + return true; +} + +bool DallasPio::write_state(bool state, uint8_t pin, bool pin_inverted) { + this->pin_ = pin; + this->pin_inverted_ = pin_inverted; + // ESP_LOGW(TAG, "DallasPio write_state %u pin %u pin_ %u", state, pin, this->pin_); + if (this->reference_ == "DS2413") { + this->ds2413_write_state_(state); + } else if (this->reference_ == "DS2406") { + this->ds2406_write_state_(state, this->crc_enabled_); + } else if (this->reference_ == "DS2408") { + // Action for DS2408 + } else { + // Default action + } + return true; +} + +/************/ +/* DS2413 */ +/************/ + +bool DallasPio::ds2413_get_state_(uint8_t &state) { + // Ref DS2413.pdf p6 of 9 + // | b7 b6 b5 b4 | b3 | b2 | b1 | b0 | + // | Complement of b3 to b0 | PIOB Output | PIOB Pin | PIOA Output | PIOA Pin | + // | | Latch State | State | Latch State | State | + // ESP_LOGCONFIG(TAG, "Dallas PIO Binary Sensor Update !"); + uint8_t results; + bool ok = false; + // Initialize One-Wire bus for this device + if (!this->check_address_()) { + this->status_set_warning(); + return false; + } + if (!this->bus_->reset()) { + ESP_LOGW(TAG, "Failed to reset One-Wire bus."); + this->status_set_warning(); + return false; + } + { + InterruptLock lock; + this->send_command_(DALLAS_COMMAND_PIO_ACCESS_READ); + results = this->bus_->read8(); + } + ok = (~results & 0x0F) == (results >> 4); + // ESP_LOGD(TAG, "results1=%02x", results); + results &= 0x0F; + // ESP_LOGD(TAG, "results2=%02x", results); + if (!this->bus_->reset()) { + ESP_LOGW(TAG, "Failed to reset One-Wire bus."); + this->status_set_warning(); + return false; + } + switch (this->pin_) { + case 0x01: // PIOA + state = (results & 0x01); + break; + case 0x02: // PIOB + state = ((results >> 2) & 0x01); + break; + default: + return false; + break; + } + return true; +} // ds2413_get_state_ + +void DallasPio::ds2413_write_state_(bool state) { + // Ref Analog Devices DS2413.pdf p8 of 19 + // | b7 b6 b5 b4 b3 b2 | b1 | b0 | + // | x x x x x x | PIOB | PIOA | + uint8_t ack = 0; + // Initialize One-Wire bus for this device + if (!this->check_address_()) { + this->status_set_warning(); + return; + } + if (!this->bus_->reset()) { + ESP_LOGW(TAG, "Failed to reset One-Wire bus."); + this->status_set_warning(); + return; + } + if ((this->pin_ >= 1) && (this->pin_ <= 2)) { + uint8_t mask = 1 << (this->pin_ - 1); + // If state different than this->pin_inverted_ + if (state ^ this->pin_inverted_) { + this->PioOutputRegister_ &= ~mask; // set bit bx to 0 + } else { + this->PioOutputRegister_ |= mask; // set bx to 1 + } + } else { + return; + } + { + InterruptLock lock; + this->send_command_(DALLAS_COMMAND_PIO_ACCESS_WRITE); + this->bus_->write8(this->PioOutputRegister_); // Send data + this->bus_->write8(~this->PioOutputRegister_); // Invert data and resend + ack = this->bus_->read8(); // 0xAA=success, 0xFF=failure + } + if (ack == DALLAS_COMMAND_PIO_ACK_SUCCESS) { + this->bus_->read8(); // Read the PIOA PIOB status byte + } else { + return; + } + if (!this->bus_->reset()) { // reset to end command + ESP_LOGW(TAG, "Failed to reset One-Wire bus."); + this->status_set_warning(); + return; + } +} // ds2413_write_state + +/************/ +/* DS2406 */ +/************/ + +bool DallasPio::ds2406_get_state_(uint8_t &state, bool use_crc = false) { + // Ref Dallas Semiconductor MAXIM DS2406.pdf + // Dallas Semiconductor Application Note 27 app27.pdf + // CHANNEL CONTROL BYTE 1 + // b7 b6 b5 b4 b3 b2 b1 b0 + // ALR IM TOG IC CHS1 CHS0 CRC1 CRC0 + // ALR=0 & IM=1, TOG=0 & IC=0 : do not change activity latch, read all bits from the selected channel & async mode + // CHANNEL CONTROL BYTE 2 + // reserved for future development, must always be 0xFF + // CHANNEL INFO BYTE + // b7 b6 b5 b4 b3 b2 b1 b0 + // Supply Number of PIOB PIOA PIOB PIOA PIOB PIOA + // Indication Channels Activity Activity Sensed Sensed Channel Channel + // 0 = no 0 = channel Latch Latch Level Level Flip-Flop Q Flip-Flop Q + // supply A only + // Please note : + // CRC1 CRC0 + // 0 0 : CRC mode 0 = CRC disabled (no CRC at all) + // 0 1 : CRC mode 1 = CRC after every byte + + uint8_t channel_control_byte_1; + uint8_t channel_control_byte_2 = 0xFF; + uint8_t channel_info_byte; + uint8_t received_crc = 0; + const char *strPIO = nullptr; + // Initialize One-Wire bus for this device + if (!this->check_address_()) { + this->status_set_warning(); + return false; + } + if (!this->bus_->reset()) { + ESP_LOGW(TAG, "Failed to reset One-Wire bus."); + this->status_set_warning(); + return false; + } + switch (this->pin_) { + case 0x01: // PIOA + channel_control_byte_1 = use_crc ? 0b10000101 : 0b10000100; // ALR=1, CHS0=1, CRC mode 1 or mode 0 + break; + case 0x02: // PIOB + channel_control_byte_1 = use_crc ? 0b10001001 : 0b10001000; // ALR=1, CHS1=1, CRC mode 1 or mode 0 + break; + default: + return false; + break; + } + { + InterruptLock lock; + this->send_command_(DALLAS_COMMAND_PIO_ACCESS_READ); + // write CHANNEL CONTROL BYTE 1 + this->bus_->write8(channel_control_byte_1); + // write CHANNEL CONTROL BYTE 2 + this->bus_->write8(channel_control_byte_2); + // read CHANNEL INFO BYTE + channel_info_byte = this->bus_->read8(); + if (use_crc) { + received_crc = this->bus_->read8(); + } + } + if (use_crc) { + ESP_LOGD(TAG, "CRC for Channel Info Byte: 0x%04X", use_crc); + if (!this->ds2406_verify_crc_(channel_control_byte_1, channel_control_byte_2, channel_info_byte, use_crc)) { + ESP_LOGW(TAG, "CRC verification failed!"); + this->status_set_warning(); + return false; + } + } + + if (!this->bus_->reset()) { + ESP_LOGW(TAG, "Failed to reset One-Wire bus."); + this->status_set_warning(); + return false; + } + // read CHANNEL INFO BYTE + bool pio_flipflop; + bool pio_sensed_level; + bool pio_activity_latch; + const bool has_channel_b = channel_info_byte & 0x40; + const bool has_supply = channel_info_byte & 0x80; + switch (this->pin_) { + case 0x01: // PIOA + pio_flipflop = channel_info_byte & 0x01; + pio_sensed_level = channel_info_byte & 0x04; + pio_activity_latch = channel_info_byte & 0x10; + strPIO = "PIOA"; + break; + case 0x02: // PIOB + pio_flipflop = channel_info_byte & 0x02; + pio_sensed_level = channel_info_byte & 0x08; + pio_activity_latch = channel_info_byte & 0x20; + strPIO = "PIOB"; + break; + default: + ESP_LOGW(TAG, "DallasPio DS2406 pin %u does not exist !", this->pin_); + this->status_set_warning(); + return false; + break; + } + if (pio_flipflop == 0) { + ESP_LOGW(TAG, "DallasPio DS2406 PIO flipflop must be 1 to read %s", strPIO); + this->status_set_warning(); + return false; + } + state = pio_sensed_level; + return true; +} // ds2406_get_state_ + +void DallasPio::ds2406_write_state_(bool state, bool use_crc = false) { + // Ref Dallas Semiconductor MAXIM DS2406.pdf + // Dallas Semiconductor Application Note 27 app27.pdf + // CHANNEL CONTROL BYTE 1 + // b7 b6 b5 b4 b3 b2 b1 b0 + // ALR IM TOG IC CHS1 CHS0 CRC1 CRC0 + // ALR=0 & IM=0, TOG=0 & IC=0 : do not change activity latch, write all bits to the selected channel & async mode + // CHS1=01 CHS0=1 : PIOA, CHS1=1 CHS0=0 : PIOB + // CHANNEL CONTROL BYTE 2 + // reserved for future development, must always be 0xFF + // CHANNEL INFO BYTE + // b7 b6 b5 b4 b3 b2 b1 b0 + // Supply Number of PIOB PIOA PIOB PIOA PIOB PIOA + // Indication Channels Activity Activity Sensed Sensed Channel Channel + // 0 = no 0 = channel Latch Latch Level Level Flip-Flop Q Flip-Flop Q + // supply A only + // Initialize One-Wire bus for this device + if (!this->check_address_()) { + this->status_set_warning(); + return; + } + if (!this->bus_->reset()) { + ESP_LOGW(TAG, "Failed to reset One-Wire bus."); + this->status_set_warning(); + return; + } + // ALR=0,IM=0,TOG=0,IC=0, PIOA:CHS1=0,CHS0=1, PIOB:CHS1=1,CHS0=0, CRC1=0, CRC0=0 + uint8_t channel_control_byte_1; + switch (this->pin_) { + case 0x01: // PIOA + channel_control_byte_1 = use_crc ? 0b00000101 : 0b00000100; + break; + case 0x02: // PIOB + channel_control_byte_1 = use_crc ? 0b00001001 : 0b00001000; + break; + default: + return; + break; + } + uint8_t channel_control_byte_2 = 0xFF; + uint8_t channel_info_byte; + { + InterruptLock lock; + this->send_command_(DALLAS_COMMAND_PIO_ACCESS_READ); + // write CHANNEL CONTROL BYTE 1 + this->bus_->write8(channel_control_byte_1); + // write CHANNEL CONTROL BYTE 2 + this->bus_->write8(channel_control_byte_2); + channel_info_byte = this->bus_->read8(); + this->bus_->write8(state ? 0xFF : 0x00); + } + if (!this->bus_->reset()) { + ESP_LOGW(TAG, "Failed to reset One-Wire bus."); + this->status_set_warning(); + return; + } +} // ds2406_write_state + +bool DallasPio::ds2406_verify_crc_(uint8_t control1, uint8_t control2, uint8_t info, uint16_t received_crc) { + uint16_t computed_crc = 0; + this->crc_reset_(); + this->crc_shift_byte_(DALLAS_COMMAND_PIO_ACCESS_READ); + this->crc_shift_byte_(control1); + this->crc_shift_byte_(control2); + this->crc_shift_byte_(info); + computed_crc = this->crc_read_(); + + ESP_LOGD(TAG, "Computed CRC: 0x%04X", computed_crc); + return computed_crc == received_crc; +} + +/************/ +/* DS2408 */ +/************/ + +bool DallasPio::ds2408_get_state_(uint8_t &state, bool use_crc = false) { + ESP_LOGW(TAG, "DallasPio DS2408 : ds2408_get_state_ to be implemented"); + this->status_set_warning(); + return false; +} + +void DallasPio::ds2408_write_state_(bool state, bool use_crc = false) { + ESP_LOGW(TAG, "DallasPio DS2408 : ds2408_write_state_ to be implemented"); + this->status_set_warning(); + return; +} + +void DallasPio::crc_reset_() { this->crc_ = 0x0000; } + +void DallasPio::crc_shift_byte_(uint8_t byte) { + for (int i = 0; i < 8; i++) { + bool mix = (this->crc_ ^ byte) & 0x01; + this->crc_ >>= 1; + if (mix) + this->crc_ ^= 0xA001; + byte >>= 1; + } +} + +uint16_t DallasPio::crc_read_() const { return this->crc_; } + +} // namespace dallas_pio +} // namespace esphome diff --git a/esphome/components/dallas_pio/dallas_pio.h b/esphome/components/dallas_pio/dallas_pio.h new file mode 100644 index 0000000000..3720f78d6c --- /dev/null +++ b/esphome/components/dallas_pio/dallas_pio.h @@ -0,0 +1,96 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/one_wire/one_wire.h" +#include "dallas_pio_constants.h" + +namespace esphome { +namespace dallas_pio { + +class DallasPio : public Component, public one_wire::OneWireDevice { + public: + DallasPio(); + std::string id_; + optional one_wire_id_; + + void set_id(const std::string &id); + void set_name(const std::string &name); + void set_address(uint64_t address); + void set_reference(const std::string &reference); + void set_crc_enabled(bool enabled); + void set_one_wire_id(const std::string &one_wire_id); + bool check_address(); + + void setup() override; + void dump_config() override; + void initialize_reference_() { + this->family_code_ = static_cast(this->address_ & 0xFF); + switch (this->family_code_) { + case DALLAS_MODEL_DS2413: + this->reference_from_address_ = "DS2413"; + break; + case DALLAS_MODEL_DS2406: + this->reference_from_address_ = "DS2406"; + break; + case DALLAS_MODEL_DS2408: + this->reference_from_address_ = "DS2408"; + break; + default: + this->reference_from_address_ = "Unknown"; + break; + } + if (this->reference_ == "") { + if (this->reference_from_address_ == "Unknown") { + this->reference_ = "DS2413"; + } else { + this->reference_ = this->reference_from_address_; + } + } + + if (this->reference_ == "DS2413") { + PioOutputRegister_ = 0xFF; + } else if (this->reference_ == "DS2406") { + // Action pour DS2406 + } else if (this->reference_ == "DS2408") { + // Action pour DS2408 + } else { + // Action par défaut + } + } + + bool read_state(uint8_t &state, uint8_t pin); + bool write_state(bool state, uint8_t pin, bool pin_inverted); + + const std::string &get_name() const { return this->name_; } + const std::string &get_id() const { return this->id_; } + uint64_t get_address() const { return this->address_; } + const std::string &get_reference() const { return this->reference_; } + uint8_t get_family_code() const { return this->family_code_; } + + protected: + std::string name_; + std::string reference_; + uint64_t address_; + std::string reference_from_address_; + uint8_t family_code_; + bool is_setup_done_ = false; + bool ds2413_get_state_(uint8_t &state); + void ds2413_write_state_(bool state); + bool ds2406_get_state_(uint8_t &state, bool use_crc); + void ds2406_write_state_(bool state, bool use_crc); + bool ds2408_get_state_(uint8_t &state, bool use_crc); + void ds2408_write_state_(bool state, bool use_crc); + uint16_t crc_; + bool crc_enabled_ = false; + bool ds2406_verify_crc_(uint8_t control1, uint8_t control2, uint8_t info, uint16_t received_crc); + void crc_reset_(); + void crc_shift_byte_(uint8_t byte); + uint16_t crc_read_() const; + // uint8_t calculate_crc_(std::initializer_list data); + uint8_t pin_; + bool pin_inverted_; + uint8_t PioOutputRegister_; +}; + +} // namespace dallas_pio +} // namespace esphome diff --git a/esphome/components/dallas_pio/dallas_pio.py b/esphome/components/dallas_pio/dallas_pio.py new file mode 100644 index 0000000000..dd0a90f5c4 --- /dev/null +++ b/esphome/components/dallas_pio/dallas_pio.py @@ -0,0 +1,62 @@ +import esphome.codegen as cg +from esphome.components import one_wire +import esphome.config_validation as cv +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME + +dallas_pio_ns = cg.esphome_ns.namespace("dallas_pio") +DallasPio = dallas_pio_ns.class_( + "DallasPio", + cg.Component, + one_wire.OneWireDevice, +) + +CONF_REFERENCE = "reference" +CONF_CRC = "crc" + + +def validate_crc_option(config_list): + for conf in config_list: + reference = conf.get("reference", "").upper() + if not reference: # Si 'reference' n'est pas défini, on passe + continue + if reference == "DS2406" and "crc" not in conf: + raise cv.Invalid("Option 'crc' is required when 'reference' is DS2406.") + if reference != "DS2406" and "crc" in conf: + raise cv.Invalid("Option 'crc' is not supported for this reference.") + return config_list + + +CONFIG_SCHEMA = cv.All( + cv.ensure_list( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(DallasPio), + cv.Optional(CONF_NAME): cv.string_strict, + cv.Required(CONF_ADDRESS): cv.hex_uint64_t, + cv.Optional(CONF_REFERENCE, default=""): cv.one_of( + "", "DS2413", "DS2406", "DS2408", upper=True + ), + cv.Optional(CONF_CRC, default=False): cv.boolean, + } + ).extend(one_wire.one_wire_device_schema()) + ), + validate_crc_option, +) + + +async def to_code(config): + for conf in config: + var = cg.new_Pvariable(conf[CONF_ID]) + if CONF_NAME in conf: + cg.add(var.set_name(conf[CONF_NAME])) + cg.add(var.set_address(conf[CONF_ADDRESS])) + if CONF_REFERENCE in conf: + cg.add(var.set_reference(conf[CONF_REFERENCE])) + if CONF_CRC in conf and conf[CONF_REFERENCE] == "DS2406": + cg.add(var.set_crc_enabled(conf[CONF_CRC])) + if CONF_ID in conf: + cg.add(var.set_id(conf[CONF_ID].id)) + if "one_wire_id" in conf: + cg.add(var.set_one_wire_id(conf["one_wire_id"].id)) + await cg.register_component(var, config) + await one_wire.register_one_wire_device(var, conf) diff --git a/esphome/components/dallas_pio/dallas_pio_constants.h b/esphome/components/dallas_pio/dallas_pio_constants.h new file mode 100644 index 0000000000..fe4655f528 --- /dev/null +++ b/esphome/components/dallas_pio/dallas_pio_constants.h @@ -0,0 +1,17 @@ +#pragma once + +namespace esphome { +namespace dallas_pio { + +static const char *const TAG = "dallas_pio"; + +static const uint8_t DALLAS_MODEL_DS2413 = 0xBA; +static const uint8_t DALLAS_MODEL_DS2406 = 0x12; +static const uint8_t DALLAS_MODEL_DS2408 = 0x29; +static const uint8_t DALLAS_COMMAND_PIO_ACCESS_READ = 0xF5; +static const uint8_t DALLAS_COMMAND_PIO_ACCESS_WRITE = 0x5A; +static const uint8_t DALLAS_COMMAND_PIO_ACK_SUCCESS = 0xAA; +static const uint8_t DALLAS_COMMAND_PIO_ACK_ERROR = 0xFF; + +} // namespace dallas_pio +} // namespace esphome diff --git a/esphome/components/dallas_pio/switch.cpp b/esphome/components/dallas_pio/switch.cpp new file mode 100644 index 0000000000..6d5f6ff0d1 --- /dev/null +++ b/esphome/components/dallas_pio/switch.cpp @@ -0,0 +1,81 @@ +#include "switch.h" + +namespace esphome { +namespace dallas_pio { + +void DallasPioSwitch::setup() { + if (this->dallas_pio_ == nullptr) { + ESP_LOGE(TAG, "DallasPioSwitch setup failed - DallasPio not set for this switch."); + return; + } + ESP_LOGI(TAG, "DallasPioSwitch setup - DallasPio set for this switch."); + + this->address_ = this->dallas_pio_->get_address(); + this->reference_ = this->dallas_pio_->get_reference(); + this->family_code_ = this->dallas_pio_->get_family_code(); +} + +void DallasPioSwitch::dump_config() { + ESP_LOGCONFIG(TAG, "Dallas PIO Switch:"); + ESP_LOGCONFIG(TAG, " dallas_pio_id: %s", this->dallas_pio_->get_id().c_str()); + if (this->reference_ == "DS2408") { + ESP_LOGCONFIG(TAG, " pin: P%c", '0' - 1 + this->pin_); // P0, P1, ... + } else { + ESP_LOGCONFIG(TAG, " pin: PIO%c", 'A' - 1 + this->pin_); // PIOA, PIOB, ... + } + ESP_LOGCONFIG(TAG, " pin inverted: %s", this->pin_inverted_ ? "true" : "false"); + ESP_LOGCONFIG(TAG, " inverted: %s", this->inverted_ ? "true" : "false"); + /* + if (this->address_ == 0) { + ESP_LOGCONFIG(TAG, " address: invalid (null)"); + } else { + ESP_LOGCONFIG(TAG, " address: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", + (uint8_t) (this->address_ >> 56), + (uint8_t) (this->address_ >> 48), + (uint8_t) (this->address_ >> 40), + (uint8_t) (this->address_ >> 32), + (uint8_t) (this->address_ >> 24), + (uint8_t) (this->address_ >> 16), + (uint8_t) (this->address_ >> 8), + (uint8_t) (this->address_)); + ESP_LOGCONFIG(TAG, " Family code: 0x%02X", this->family_code_); + ESP_LOGCONFIG(TAG, " Reference: %s", this->reference_.c_str()); + } + */ + ESP_LOGCONFIG(TAG, " Name: %s", this->EntityBase::name_.c_str()); + + // this->component_->log_one_wire_device(); + // LOG_UPDATE_INTERVAL(this); +} + +void DallasPioSwitch::loop() {} + +void DallasPioSwitch::write_state(bool state) { + ESP_LOGD(TAG, "DallasPioSwitch write_state %u", state); + + if (this->inverted_) { + state = !state; + } + + if (this->dallas_pio_->check_address() == 0) { + this->status_set_warning(); + return; + } + this->status_clear_warning(); + + if (this->reference_ == "DS2413") { + if (!this->dallas_pio_->write_state(state, this->pin_, this->pin_inverted_)) { + return; + } + } else if (this->reference_ == "DS2406") { + // Action pour DS2406 + } else if (this->reference_ == "DS2408") { + // Action pour DS2408 + } else { + // Action par défaut + } + this->publish_state(state); // Set state in ESPHome +} + +} // namespace dallas_pio +} // namespace esphome \ No newline at end of file diff --git a/esphome/components/dallas_pio/switch.h b/esphome/components/dallas_pio/switch.h new file mode 100644 index 0000000000..dedfa239f8 --- /dev/null +++ b/esphome/components/dallas_pio/switch.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "dallas_pio_constants.h" +#include "dallas_pio.h" + +namespace esphome { +namespace dallas_pio { + +class DallasPioSwitch : public Component, public switch_::Switch { + public: + DallasPioSwitch() : dallas_pio_(nullptr), pio_pin_(0) {} + DallasPioSwitch(DallasPio *dallas_pio, uint8_t pio_pin) : dallas_pio_(dallas_pio), pio_pin_(pio_pin) {} + void setup() override; + void dump_config() override; + void loop() override; + void write_state(bool state) override; + + using EntityBase::set_name; + void set_dallas_pio(DallasPio *dallas_pio) { this->dallas_pio_ = dallas_pio; } + void set_address(uint64_t address) { this->address_ = address; } + void set_pin(uint8_t pin) { this->pin_ = pin; } + void set_pin_inverted(bool pin_inverted) { this->pin_inverted_ = pin_inverted; } + void set_pin_mode(bool input, bool output) { + this->is_input_ = input; + this->is_output_ = output; + } + void set_inverted(bool inverted) { this->inverted_ = inverted; } + + protected: + DallasPio *dallas_pio_; + uint8_t pio_pin_; + std::string reference_; + uint8_t family_code_; + uint64_t address_; + uint8_t pin_; + bool pin_inverted_; + bool inverted_; + bool is_input_; + bool is_output_; +}; + +} // namespace dallas_pio +} // namespace esphome diff --git a/esphome/components/dallas_pio/switch.py b/esphome/components/dallas_pio/switch.py new file mode 100644 index 0000000000..2895f4770f --- /dev/null +++ b/esphome/components/dallas_pio/switch.py @@ -0,0 +1,68 @@ +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import CONF_INVERTED, CONF_MODE, CONF_PIN + +DEPENDENCIES = ["switch", "dallas_pio"] +AUTO_LOAD = ["switch"] + +dallas_pio_ns = cg.esphome_ns.namespace("dallas_pio") +DallasPioSwitch = dallas_pio_ns.class_( + "DallasPioSwitch", + cg.Component, + switch.Switch, +) + + +def validate_mode(value): + if value.get("output", False) is not True: + raise cv.Invalid( + "The 'output' field must always be 'true' for Dallas PIO switches." + ) + if value.get("input", False) is True: + raise cv.Invalid("The 'input' field must be 'false' if 'output' is 'true'.") + return value + + +CUSTOM_PIN_SCHEMA = cv.Schema( + { + cv.Required("number"): cv.one_of("PIOA", "PIOB", upper=True), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.Optional(CONF_MODE, default={"output": True}): validate_mode, + } +) + +CONFIG_SCHEMA = switch.switch_schema( + DallasPioSwitch, +).extend( + { + cv.Required(CONF_PIN): CUSTOM_PIN_SCHEMA, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.Required("dallas_pio_id"): cv.use_id(dallas_pio_ns.class_("DallasPio")), + } +) + + +async def to_code(config): + var = await switch.new_switch(config) + await cg.register_component(var, config) + + dallas_pio_ref = await cg.get_variable(config["dallas_pio_id"]) + cg.add(var.set_dallas_pio(dallas_pio_ref)) + # cg.add(dallas_pio_ref.setup()) + # cg.add(var.set_address(dallas_pio_ref.get_address())) + + # Extract pin configuration + pin_config = config[CONF_PIN] + pin_number = pin_config["number"] # "PIOA" or "PIOB" + pin_inverted = pin_config[CONF_INVERTED] + pin_mode = pin_config[CONF_MODE] + # C++ configuration association + if pin_number == "PIOA": + cg.add(var.set_pin(0x01)) # PIOA assiociated to 0x01 + elif pin_number == "PIOB": + cg.add(var.set_pin(0x02)) # PIOB assiociated to 0x02 + cg.add(var.set_pin_inverted(pin_inverted)) + cg.add(var.set_pin_mode(pin_mode.get("input", False), pin_mode.get("output", True))) + if CONF_INVERTED in config: + cg.add(var.set_inverted(config[CONF_INVERTED]))