From dfb0defb60b08ebddbab834c78e7d0f5e3039822 Mon Sep 17 00:00:00 2001 From: Guido Schreuder Date: Wed, 21 Feb 2024 07:25:43 +0100 Subject: [PATCH] Add boolean sensor --- esphome/components/ebus/__init__.py | 59 +++++++++- esphome/components/ebus/ebus_component.h | 9 +- esphome/components/ebus/sensor/__init__.py | 105 +++++------------- .../components/ebus/sensor/ebus_sensor.cpp | 57 ---------- esphome/components/ebus/sensor/ebus_sensor.h | 27 +---- esphome/components/ebus/telegram.cpp | 7 +- esphome/components/ebus/telegram.h | 2 +- tests/components/ebus/test.esp32.yaml | 65 ++++++----- 8 files changed, 136 insertions(+), 195 deletions(-) diff --git a/esphome/components/ebus/__init__.py b/esphome/components/ebus/__init__.py index 117e35e449..4591daaa5b 100644 --- a/esphome/components/ebus/__init__.py +++ b/esphome/components/ebus/__init__.py @@ -6,12 +6,14 @@ from esphome.const import ( CONF_ID, CONF_TX_PIN, CONF_RX_PIN, + CONF_ADDRESS, + CONF_COMMAND, + CONF_PAYLOAD, + CONF_POSITION, ) -# TODO: make ebus primary address optional for only listening on the bus -# TODO: send identification response ewhen requested -# TODO: add binary_sensor +# TODO: send identification response when requested # TODO: add debug mode that logs all messages on the bus # TODO: investigate using UART component, but that does not seem to expose the UART NUM @@ -29,6 +31,21 @@ CONF_POLL_INTERVAL = "poll_interval" CONF_UART = "uart" CONF_NUM = "num" +CONF_TELEGRAM = "telegram" +CONF_SEND_POLL = "send_poll" +CONF_DECODE = "decode" + +SYN = 0xAA +ESC = 0xA9 + + +def validate_ebus_address(address): + if address == SYN: + raise vol.Invalid("SYN symbol (0xAA) is not a valid address") + if address == ESC: + raise vol.Invalid("ESC symbol (0xA9) is not a valid address") + return cv.hex_uint8_t(address) + def is_primary_nibble(value): return (value & 0x0F) in {0x00, 0x01, 0x03, 0x07, 0x0F} @@ -44,6 +61,29 @@ def validate_primary_address(value): EbusComponent = ebus_ns.class_("EbusComponent", cg.Component) +def create_decode_schema(options): + return cv.Schema( + { + cv.Optional(CONF_POSITION, default=0): cv.int_range(0, 15), + } + ).extend(options) + + +def create_telegram_schema(decode_options): + return { + cv.GenerateID(CONF_EBUS_ID): cv.use_id(EbusComponent), + cv.Required(CONF_TELEGRAM): cv.Schema( + { + cv.Optional(CONF_SEND_POLL, default=False): cv.boolean, + cv.Optional(CONF_ADDRESS): validate_ebus_address, + cv.Required(CONF_COMMAND): cv.hex_uint16_t, + cv.Required(CONF_PAYLOAD): cv.Schema([cv.hex_uint8_t]), + cv.Optional(CONF_DECODE): create_decode_schema(decode_options), + } + ), + } + + CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -80,3 +120,16 @@ async def to_code(config): cg.add(var.set_history_queue_size(config[CONF_HISTORY_QUEUE_SIZE])) cg.add(var.set_command_queue_size(config[CONF_COMMAND_QUEUE_SIZE])) cg.add(var.set_update_interval(config[CONF_POLL_INTERVAL].total_milliseconds)) + + +def sensor_base_config(sensor_base, config): + cg.add(sensor_base.set_send_poll(config[CONF_TELEGRAM][CONF_SEND_POLL])) + if CONF_ADDRESS in config[CONF_TELEGRAM]: + cg.add(sensor_base.set_address(config[CONF_TELEGRAM][CONF_ADDRESS])) + cg.add(sensor_base.set_command(config[CONF_TELEGRAM][CONF_COMMAND])) + cg.add(sensor_base.set_payload(config[CONF_TELEGRAM][CONF_PAYLOAD])) + cg.add( + sensor_base.set_response_read_position( + config[CONF_TELEGRAM][CONF_DECODE][CONF_POSITION] + ) + ) diff --git a/esphome/components/ebus/ebus_component.h b/esphome/components/ebus/ebus_component.h index a197598e9c..6fc6aafa7d 100644 --- a/esphome/components/ebus/ebus_component.h +++ b/esphome/components/ebus/ebus_component.h @@ -26,8 +26,13 @@ class EbusReceiver { class EbusSender { public: EbusSender() {} - virtual void set_primary_address(uint8_t) = 0; + void set_primary_address(uint8_t primary_address) { this->primary_address_ = primary_address; } + void set_address(uint8_t address) { this->address_ = Elf::to_secondary(address); } virtual optional prepare_command() = 0; + + protected: + uint8_t primary_address_; + uint8_t address_ = SYN; }; class EbusComponent : public PollingComponent { @@ -52,7 +57,7 @@ class EbusComponent : public PollingComponent { void update() override; protected: - uint8_t primary_address_ = SYN; + uint8_t primary_address_; uint8_t max_tries_; uint8_t max_lock_counter_; uint8_t history_queue_size_; diff --git a/esphome/components/ebus/sensor/__init__.py b/esphome/components/ebus/sensor/__init__.py index f1a64635b3..57281f7ca7 100644 --- a/esphome/components/ebus/sensor/__init__.py +++ b/esphome/components/ebus/sensor/__init__.py @@ -1,100 +1,47 @@ -import voluptuous as vol import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( - CONF_SENSORS, - CONF_SOURCE, - CONF_COMMAND, - CONF_PAYLOAD, - CONF_POSITION, CONF_BYTES, ) -from .. import EbusComponent, CONF_EBUS_ID, ebus_ns +from .. import ( + CONF_EBUS_ID, + CONF_TELEGRAM, + CONF_DECODE, + ebus_ns, + create_telegram_schema, + sensor_base_config, +) AUTO_LOAD = ["ebus"] EbusSensor = ebus_ns.class_("EbusSensor", sensor.Sensor, cg.Component) -CONF_TELEGRAM = "telegram" -CONF_SEND_POLL = "send_poll" -CONF_ADDRESS = "address" -CONF_DECODE = "decode" CONF_DIVIDER = "divider" - -SYN = 0xAA -ESC = 0xA9 - - -def validate_address(address): - if address == SYN: - raise vol.Invalid("SYN symbol (0xAA) is not a valid address") - if address == ESC: - raise vol.Invalid("ESC symbol (0xA9) is not a valid address") - return cv.hex_uint8_t(address) - - -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(EbusSensor), - cv.GenerateID(CONF_EBUS_ID): cv.use_id(EbusComponent), - cv.Required(CONF_SENSORS): cv.ensure_list( - sensor.sensor_schema().extend( - { - cv.GenerateID(): cv.declare_id(EbusSensor), - cv.Required(CONF_TELEGRAM): cv.Schema( - { - cv.Optional(CONF_SEND_POLL, default=False): cv.boolean, - cv.Optional(CONF_ADDRESS): validate_address, - cv.Required(CONF_COMMAND): cv.hex_uint16_t, - cv.Required(CONF_PAYLOAD): cv.Schema([cv.hex_uint8_t]), - cv.Optional(CONF_DECODE): cv.Schema( - { - cv.Optional(CONF_POSITION, default=0): cv.int_range( - 0, 15 - ), - cv.Required(CONF_BYTES): cv.int_range(1, 4), - cv.Required(CONF_DIVIDER): cv.float_, - } - ), - } - ), - } - ) - ), - } -) +CONFIG_SCHEMA = ( + sensor.sensor_schema(EbusSensor).extend( + create_telegram_schema( + { + cv.Required(CONF_BYTES): cv.int_range(1, 4), + cv.Required(CONF_DIVIDER): cv.float_, + } + ) + ) +).extend(cv.COMPONENT_SCHEMA) async def to_code(config): ebus = await cg.get_variable(config[CONF_EBUS_ID]) + sens = await sensor.new_sensor(config) - for i, conf in enumerate(config[CONF_SENSORS]): - print(f"Sensor: {i}, {conf}") - sens = await sensor.new_sensor(conf) - if CONF_SOURCE in conf[CONF_TELEGRAM]: - cg.add(sens.set_source(conf[CONF_TELEGRAM][CONF_SOURCE])) + sensor_base_config(sens, config) - if CONF_ADDRESS in conf[CONF_TELEGRAM]: - cg.add(sens.set_address(conf[CONF_TELEGRAM][CONF_ADDRESS])) - cg.add(sens.set_command(conf[CONF_TELEGRAM][CONF_COMMAND])) - cg.add(sens.set_payload(conf[CONF_TELEGRAM][CONF_PAYLOAD])) - cg.add( - sens.set_response_read_position( - conf[CONF_TELEGRAM][CONF_DECODE][CONF_POSITION] - ) - ) - cg.add( - sens.set_response_read_bytes(conf[CONF_TELEGRAM][CONF_DECODE][CONF_BYTES]) - ) - cg.add( - sens.set_response_read_divider( - conf[CONF_TELEGRAM][CONF_DECODE][CONF_DIVIDER] - ) - ) - - cg.add(ebus.add_receiver(sens)) - cg.add(ebus.add_sender(sens)) + cg.add(sens.set_response_read_bytes(config[CONF_TELEGRAM][CONF_DECODE][CONF_BYTES])) + cg.add( + sens.set_response_read_divider(config[CONF_TELEGRAM][CONF_DECODE][CONF_DIVIDER]) + ) + cg.add(ebus.add_receiver(sens)) + cg.add(ebus.add_sender(sens)) diff --git a/esphome/components/ebus/sensor/ebus_sensor.cpp b/esphome/components/ebus/sensor/ebus_sensor.cpp index b5ef4c9b24..2ee9caddc8 100644 --- a/esphome/components/ebus/sensor/ebus_sensor.cpp +++ b/esphome/components/ebus/sensor/ebus_sensor.cpp @@ -1,43 +1,9 @@ #include "ebus_sensor.h" -// TODO: remove -#define GET_BYTE(CMD, I) ((uint8_t) (((CMD) >> 8 * (I)) & 0XFF)) - namespace esphome { namespace ebus { -void EbusSensor::dump_config() { - ESP_LOGCONFIG(TAG, "EbusSensor"); - ESP_LOGCONFIG(TAG, " message:"); - ESP_LOGCONFIG(TAG, " send_poll: %s", this->send_poll_ ? "true" : "false"); - if (this->address_ == SYN) { - ESP_LOGCONFIG(TAG, " address: N/A"); - } else { - ESP_LOGCONFIG(TAG, " address: 0x%02x", this->address_); - } - ESP_LOGCONFIG(TAG, " command: 0x%04x", this->command_); -}; - -void EbusSensor::set_primary_address(uint8_t primary_address) { this->primary_address_ = primary_address; } -void EbusSensor::set_send_poll(bool send_poll) { this->send_poll_ = send_poll; } -void EbusSensor::set_address(uint8_t address) { this->address_ = Elf::to_secondary(address); } -void EbusSensor::set_command(uint16_t command) { this->command_ = command; } -void EbusSensor::set_payload(const std::vector &payload) { this->payload_ = payload; } -void EbusSensor::set_response_read_position(uint8_t response_position) { this->response_position_ = response_position; } -void EbusSensor::set_response_read_bytes(uint8_t response_bytes) { this->response_bytes_ = response_bytes; } -void EbusSensor::set_response_read_divider(float response_divider) { this->response_divider_ = response_divider; } - -optional EbusSensor::prepare_command() { - optional command; - if (this->send_poll_) { - command = SendCommand( // - this->primary_address_, this->address_, GET_BYTE(this->command_, 1), GET_BYTE(this->command_, 0), - this->payload_.size(), &this->payload_[0]); - } - return command; -} - void EbusSensor::process_received(Telegram telegram) { if (!is_mine(telegram)) { return; @@ -45,32 +11,9 @@ void EbusSensor::process_received(Telegram telegram) { this->publish_state(to_float(telegram, this->response_position_, this->response_bytes_, this->response_divider_)); } -uint32_t EbusSensor::get_response_bytes(Telegram &telegram, uint8_t start, uint8_t length) { - uint32_t result = 0; - for (uint8_t i = 0; i < 4 && i < length; i++) { - result = result | (telegram.get_response_byte(start + i) << (i * 8)); - } - return result; -} - float EbusSensor::to_float(Telegram &telegram, uint8_t start, uint8_t length, float divider) { return get_response_bytes(telegram, start, length) / divider; } -bool EbusSensor::is_mine(Telegram &telegram) { - if (this->address_ != SYN && this->address_ != telegram.get_zz()) { - return false; - } - if (telegram.get_command() != this->command_) { - return false; - } - for (int i = 0; i < this->payload_.size(); i++) { - if (this->payload_[i] != telegram.get_request_byte(i)) { - return false; - } - } - return true; -} - } // namespace ebus } // namespace esphome diff --git a/esphome/components/ebus/sensor/ebus_sensor.h b/esphome/components/ebus/sensor/ebus_sensor.h index 16257ea1a7..4ee0ec0b11 100644 --- a/esphome/components/ebus/sensor/ebus_sensor.h +++ b/esphome/components/ebus/sensor/ebus_sensor.h @@ -1,42 +1,23 @@ #pragma once -#include "../ebus_component.h" +#include "../ebus_sensor_base.h" #include "esphome/components/sensor/sensor.h" namespace esphome { namespace ebus { -class EbusSensor : public EbusReceiver, public EbusSender, public sensor::Sensor, public Component { +class EbusSensor : public EbusSensorBase, public sensor::Sensor { public: EbusSensor() {} - void dump_config() override; - - void set_primary_address(uint8_t /*primary_address*/) override; - void set_send_poll(bool /*send_poll*/); - void set_address(uint8_t /*address*/); - void set_command(uint16_t /*command*/); - void set_payload(const std::vector & /*payload*/); - - void set_response_read_position(uint8_t /*response_position*/); - void set_response_read_bytes(uint8_t /*response_bytes*/); - void set_response_read_divider(float /*response_divider*/); + void set_response_read_bytes(uint8_t response_bytes) { this->response_bytes_ = response_bytes; } + void set_response_read_divider(float response_divider) { this->response_divider_ = response_divider; } void process_received(Telegram /*telegram*/) override; - optional prepare_command() override; - // TODO: refactor these - uint32_t get_response_bytes(Telegram &telegram, uint8_t start, uint8_t length); float to_float(Telegram &telegram, uint8_t start, uint8_t length, float divider); - bool is_mine(Telegram &telegram); protected: - uint8_t primary_address_; - bool send_poll_; - uint8_t address_ = SYN; - uint16_t command_; - std::vector payload_{}; - uint8_t response_position_; uint8_t response_bytes_; float response_divider_; }; diff --git a/esphome/components/ebus/telegram.cpp b/esphome/components/ebus/telegram.cpp index a15bb8803f..e71c4fcb1c 100644 --- a/esphome/components/ebus/telegram.cpp +++ b/esphome/components/ebus/telegram.cpp @@ -104,19 +104,18 @@ bool Telegram::is_request_valid() { SendCommand::SendCommand() { this->state_ = TelegramState::endCompleted; } -SendCommand::SendCommand(uint8_t qq, uint8_t zz, uint8_t pb, uint8_t sb, uint8_t nn, uint8_t *data) { +SendCommand::SendCommand(uint8_t qq, uint8_t zz, uint16_t command, uint8_t nn, uint8_t *data) { this->state_ = TelegramState::waitForSend; this->push_req_data(qq); this->push_req_data(zz); - this->push_req_data(pb); - this->push_req_data(sb); + this->push_req_data(command >> 8); + this->push_req_data(command & 0xFF); this->push_req_data(nn); for (int i = 0; i < nn; i++) { this->push_req_data(data[i]); } this->push_req_data(this->request_rolling_crc_); } - bool SendCommand::can_retry(int8_t max_tries) { return this->tries_count_++ < max_tries; } uint8_t SendCommand::get_crc() { return this->request_rolling_crc_; } diff --git a/esphome/components/ebus/telegram.h b/esphome/components/ebus/telegram.h index a5c598f373..48afd9c5c5 100644 --- a/esphome/components/ebus/telegram.h +++ b/esphome/components/ebus/telegram.h @@ -132,7 +132,7 @@ class Telegram : public TelegramBase { class SendCommand : public TelegramBase { public: SendCommand(); - SendCommand(uint8_t qq, uint8_t zz, uint8_t pb, uint8_t sb, uint8_t nn, uint8_t *data); + SendCommand(uint8_t qq, uint8_t zz, uint16_t command, uint8_t nn, uint8_t *data); bool can_retry(int8_t max_tries); uint8_t get_crc(); diff --git a/tests/components/ebus/test.esp32.yaml b/tests/components/ebus/test.esp32.yaml index df49269859..267024c368 100644 --- a/tests/components/ebus/test.esp32.yaml +++ b/tests/components/ebus/test.esp32.yaml @@ -2,7 +2,7 @@ ebus: primary_address: 0x00 history_queue_size: 32 command_queue_size: 16 - poll_interval: "15s" + poll_interval: "60s" uart: num: 1 tx_pin: 33 @@ -10,28 +10,41 @@ ebus: sensor: - platform: ebus - sensors: - - name: "Water Pressure" - id: zebus_water_pressure - telegram: - destination: 0x03 - command: 0xb509 - payload: [0x0d, 0x02, 0x00] - decode: - position: 0 - bytes: 2 - divider: 1000.0 - device_class: "pressure" - unit_of_measurement: bar - state_class: "measurement" - accuracy_decimals: 3 - internal: false - filters: - - clamp: - min_value: 0 - max_value: 4 - ignore_out_of_range: true - - median: - window_size: 5 - send_every: 3 - send_first_at: 3 + name: "Water Pressure" + id: zebus_water_pressure + telegram: + send_poll: true + address: 0x03 + command: 0xb509 + payload: [0x0d, 0x02, 0x00] + decode: + position: 0 + bytes: 2 + divider: 1000.0 + device_class: "pressure" + unit_of_measurement: bar + state_class: "measurement" + accuracy_decimals: 3 + internal: false + filters: + - clamp: + min_value: 0 + max_value: 4 + ignore_out_of_range: true + - median: + window_size: 5 + send_every: 3 + send_first_at: 3 + +binary_sensor: + - platform: ebus + name: "Thermostat in Quick Veto" + id: zebus_in_quick_veto + telegram: + send_poll: true + address: 0x10 + command: 0xb509 + payload: [0x0d, 0x16, 0x00] + decode: + position: 0 + mask: 0x01