diff --git a/esphome/components/optolink/__init__.py b/esphome/components/optolink/__init__.py index 49630e804b..2bdb253adb 100644 --- a/esphome/components/optolink/__init__.py +++ b/esphome/components/optolink/__init__.py @@ -1,35 +1,40 @@ +from esphome import core from esphome import pins import esphome.codegen as cg -from esphome.components import text_sensor as ts import esphome.config_validation as cv from esphome.const import ( + CONF_DIV_RATIO, CONF_ID, CONF_LOGGER, CONF_PROTOCOL, CONF_RX_PIN, - CONF_STATE, CONF_TX_PIN, + CONF_UPDATE_INTERVAL, ) from esphome.core import CORE CODEOWNERS = ["@j0ta29"] DEPENDENCIES = [] -AUTO_LOAD = ["sensor", "binary_sensor", "text_sensor", "number", "select", "switch"] +AUTO_LOAD = [] MULTI_CONF = False -CONF_DEVICE_INFO = "device_info" optolink_ns = cg.esphome_ns.namespace("optolink") CONF_OPTOLINK_ID = "optolink_id" OptolinkComponent = optolink_ns.class_("Optolink", cg.Component) -StateSensor = optolink_ns.class_( - "OptolinkStateSensor", ts.TextSensor, cg.PollingComponent +CONF_OPTOLINK_ID = "optolink_id" +SENSOR_BASE_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_OPTOLINK_ID): cv.use_id(OptolinkComponent), + cv.Optional(CONF_UPDATE_INTERVAL, default="10s"): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800)), + ), + cv.Optional(CONF_DIV_RATIO, default=1): cv.one_of( + 1, 10, 100, 1000, 3600, int=True + ), + } ) -STATE_SENSOR_ID = "state_sensor_id" -DeviceInfoSensor = optolink_ns.class_( - "OptolinkDeviceInfoSensor", ts.TextSensor, cg.PollingComponent -) -DEVICE_INFO_SENSOR_ID = "device_info_sensor_id" def required_on_esp32(attribute): @@ -47,8 +52,6 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(OptolinkComponent), - cv.GenerateID(STATE_SENSOR_ID): cv.declare_id(StateSensor), - cv.GenerateID(DEVICE_INFO_SENSOR_ID): cv.declare_id(DeviceInfoSensor), cv.Required(CONF_PROTOCOL): cv.one_of("P300", "KW"), cv.Optional(CONF_RX_PIN): cv.All( cv.only_on_esp32, @@ -59,8 +62,6 @@ CONFIG_SCHEMA = cv.All( pins.internal_gpio_output_pin_schema, ), cv.Optional(CONF_LOGGER, default=False): cv.boolean, - cv.Optional(CONF_STATE): cv.string, - cv.Optional(CONF_DEVICE_INFO): cv.string, } ).extend(cv.COMPONENT_SCHEMA), cv.only_with_arduino, @@ -71,7 +72,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - cg.add_library("VitoWiFi", "1.0.2") + cg.add_library("VitoWiFi", "1.1.2") cg.add_define( "VITOWIFI_PROTOCOL", cg.RawExpression(f"Optolink{config[CONF_PROTOCOL]}") @@ -80,32 +81,6 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_logger_enabled(config[CONF_LOGGER])) - if CONF_STATE in config: - debugSensor = cg.new_Pvariable(config[STATE_SENSOR_ID], config[CONF_STATE], var) - await ts.register_text_sensor( - debugSensor, - { - "id": config[STATE_SENSOR_ID], - "name": config[CONF_STATE], - "disabled_by_default": "false", - }, - ) - await cg.register_component(debugSensor, config) - - if CONF_DEVICE_INFO in config: - debugSensor = cg.new_Pvariable( - config[DEVICE_INFO_SENSOR_ID], config[CONF_DEVICE_INFO], var - ) - await ts.register_text_sensor( - debugSensor, - { - "id": config[DEVICE_INFO_SENSOR_ID], - "name": config[CONF_DEVICE_INFO], - "disabled_by_default": "false", - }, - ) - await cg.register_component(debugSensor, config) - if CORE.is_esp32: cg.add(var.set_rx_pin(config[CONF_RX_PIN]["number"])) cg.add(var.set_tx_pin(config[CONF_TX_PIN]["number"])) diff --git a/esphome/components/optolink/binary_sensor.py b/esphome/components/optolink/binary_sensor.py deleted file mode 100644 index f55b9c6d16..0000000000 --- a/esphome/components/optolink/binary_sensor.py +++ /dev/null @@ -1,30 +0,0 @@ -from esphome import core -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import binary_sensor -from esphome.const import CONF_ID, CONF_ADDRESS, CONF_UPDATE_INTERVAL -from . import OptolinkComponent, optolink_ns, CONF_OPTOLINK_ID - -OptolinkBinarySensor = optolink_ns.class_( - "OptolinkBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent -) -CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(OptolinkBinarySensor).extend( - { - cv.GenerateID(CONF_OPTOLINK_ID): cv.use_id(OptolinkComponent), - cv.Required(CONF_ADDRESS): cv.hex_uint32_t, - cv.Optional(CONF_UPDATE_INTERVAL, default="10s"): cv.All( - cv.positive_time_period_milliseconds, - cv.Range(min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800)), - ), - } -) - - -async def to_code(config): - component = await cg.get_variable(config[CONF_OPTOLINK_ID]) - var = cg.new_Pvariable(config[CONF_ID], component) - - await cg.register_component(var, config) - await binary_sensor.register_binary_sensor(var, config) - - cg.add(var.set_address(config[CONF_ADDRESS])) diff --git a/esphome/components/optolink/binary_sensor/__init__.py b/esphome/components/optolink/binary_sensor/__init__.py new file mode 100644 index 0000000000..52fb20c842 --- /dev/null +++ b/esphome/components/optolink/binary_sensor/__init__.py @@ -0,0 +1,34 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_ADDRESS, CONF_ID +from .. import SENSOR_BASE_SCHEMA, optolink_ns, CONF_OPTOLINK_ID + +DEPENDENCIES = ["optolink"] +CODEOWNERS = ["@j0ta29"] + + +OptolinkBinarySensor = optolink_ns.class_( + "OptolinkBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent +) + +CONFIG_SCHEMA = ( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(OptolinkBinarySensor), + cv.Required(CONF_ADDRESS): cv.hex_uint32_t, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(SENSOR_BASE_SCHEMA) +) + + +async def to_code(config): + component = await cg.get_variable(config[CONF_OPTOLINK_ID]) + var = cg.new_Pvariable(config[CONF_ID], component) + + await cg.register_component(var, config) + await binary_sensor.register_binary_sensor(var, config) + + cg.add(var.set_address(config[CONF_ADDRESS])) diff --git a/esphome/components/optolink/binary_sensor/optolink_binary_sensor.h b/esphome/components/optolink/binary_sensor/optolink_binary_sensor.h new file mode 100644 index 0000000000..67f8e99307 --- /dev/null +++ b/esphome/components/optolink/binary_sensor/optolink_binary_sensor.h @@ -0,0 +1,31 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "../optolink.h" +#include "../datapoint_component.h" + +namespace esphome { +namespace optolink { + +class OptolinkBinarySensor : public DatapointComponent, + public esphome::binary_sensor::BinarySensor, + public esphome::PollingComponent { + public: + OptolinkBinarySensor(Optolink *optolink) : DatapointComponent(optolink) { + set_bytes(1); + set_div_ratio(1); + } + + protected: + void setup() override { setup_datapoint(); } + void update() override { datapoint_read_request(); } + + const StringRef &get_component_name() override { return get_name(); } + void datapoint_value_changed(uint8_t state) override { publish_state(state); }; +}; +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/datapoint_component.cpp b/esphome/components/optolink/datapoint_component.cpp new file mode 100644 index 0000000000..3b552e1c01 --- /dev/null +++ b/esphome/components/optolink/datapoint_component.cpp @@ -0,0 +1,316 @@ +#ifdef USE_ARDUINO + +#include "datapoint_component.h" +#include "optolink.h" +#include "esphome/components/api/api_server.h" + +namespace esphome { +namespace optolink { + +static const char *const TAG = "optolink.datapoint_component"; +static std::vector hass_subscriptions_; + +void DatapointComponent::setup_datapoint() { + switch (div_ratio_) { + case 0: + datapoint_ = new Datapoint(get_component_name().c_str(), "optolink", address_, writeable_); + datapoint_->setLength(bytes_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { + uint8_t buffer[bytes_]; + dp_value.getRaw(buffer); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_INFO + char print_buffer[bytes_ * 2 + 1]; + dp_value.getString(print_buffer, sizeof(print_buffer)); + ESP_LOGI(TAG, "recieved data for datapoint %s: %s", dp.getName(), print_buffer); +#endif + datapoint_value_changed((uint8_t *) buffer, bytes_); + read_retries_ = 0; + }); + break; + case 1: + switch (bytes_) { + case 1: + datapoint_ = new Datapoint(get_component_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { + ESP_LOGI(TAG, "recieved data for datapoint %s: %d", dp.getName(), dp_value.getU8()); + datapoint_value_changed(dp_value.getU8()); + read_retries_ = 0; + }); + break; + case 2: + datapoint_ = new Datapoint(get_component_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { + ESP_LOGI(TAG, "recieved data for datapoint %s: %d", dp.getName(), dp_value.getU16()); + datapoint_value_changed(dp_value.getU16()); + read_retries_ = 0; + }); + break; + case 4: + datapoint_ = new Datapoint(get_component_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { + ESP_LOGI(TAG, "recieved data for datapoint %s: %d", dp.getName(), dp_value.getU32()); + datapoint_value_changed((uint32_t) dp_value.getU32()); + read_retries_ = 0; + }); + break; + default: + unfitting_value_type(); + } + break; + case 10: + switch (bytes_) { + case 1: + datapoint_ = new Datapoint(get_component_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { + ESP_LOGI(TAG, "recieved data for datapoint %s: %f", dp.getName(), dp_value.getFloat()); + datapoint_value_changed(dp_value.getFloat()); + read_retries_ = 0; + }); + break; + case 2: + datapoint_ = new Datapoint(get_component_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { + ESP_LOGI(TAG, "recieved data for datapoint %s: %f", dp.getName(), dp_value.getFloat()); + datapoint_value_changed(dp_value.getFloat()); + read_retries_ = 0; + }); + break; + default: + unfitting_value_type(); + } + break; + case 100: + switch (bytes_) { + case 2: + datapoint_ = new Datapoint(get_component_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { + ESP_LOGI(TAG, "recieved data for datapoint %s: %f", dp.getName(), dp_value.getFloat()); + datapoint_value_changed(dp_value.getFloat()); + read_retries_ = 0; + }); + break; + default: + unfitting_value_type(); + } + break; + case 1000: + switch (bytes_) { + case 4: + datapoint_ = new Datapoint(get_component_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { + ESP_LOGI(TAG, "recieved data for datapoint %s: %f", dp.getName(), dp_value.getFloat()); + datapoint_value_changed(dp_value.getFloat()); + read_retries_ = 0; + }); + break; + } + break; + case 3600: + switch (bytes_) { + case 4: + datapoint_ = new Datapoint(get_component_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { + ESP_LOGI(TAG, "recieved data for datapoint %s: %f", dp.getName(), dp_value.getFloat()); + datapoint_value_changed(dp_value.getFloat()); + read_retries_ = 0; + }); + break; + } + break; + default: + unfitting_value_type(); + } +} + +void DatapointComponent::datapoint_read_request() { + if (is_dp_value_writing_outstanding) { + ESP_LOGI(TAG, "read request for %s deferred due to outstanding write request", get_component_name().c_str()); + datapoint_write_request(dp_value_outstanding); + } else { + if (read_retries_ == 0 || read_retries_ >= MAX_RETRIES_UNTIL_RESET) { + if (optolink_->read_value(datapoint_)) { + read_retries_ = 1; + } + } else { + read_retries_++; + ESP_LOGW(TAG, "%d. read request for %s rejected due to outstanding running request - increase update_interval!", + read_retries_, get_component_name().c_str()); + } + } +} + +void DatapointComponent::datapoint_value_changed(float value) { + ESP_LOGW(TAG, "unused value update by sensor %s", get_component_name().c_str()); +} + +void DatapointComponent::datapoint_value_changed(uint8_t value) { + ESP_LOGW(TAG, "unused value update by sensor %s", get_component_name().c_str()); +} + +void DatapointComponent::datapoint_value_changed(uint16_t value) { + ESP_LOGW(TAG, "unused value update by sensor %s", get_component_name().c_str()); +} + +void DatapointComponent::datapoint_value_changed(uint32_t value) { + ESP_LOGW(TAG, "unused value update by sensor %s", get_component_name().c_str()); +} + +void DatapointComponent::datapoint_value_changed(std::string value) { + ESP_LOGW(TAG, "unused value update by sensor %s", get_component_name().c_str()); +} + +void DatapointComponent::datapoint_value_changed(uint8_t *value, size_t length) { + ESP_LOGW(TAG, "unused value update by sensor %s", get_component_name().c_str()); +} + +void DatapointComponent::datapoint_write_request(DPValue dp_value) { + if (!writeable_) { + optolink_->set_state("trying to control not writable datapoint %s", get_component_name().c_str()); + ESP_LOGE(TAG, "trying to control not writable datapoint %s", get_component_name().c_str()); + } else if (datapoint_ != nullptr) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_INFO + char buffer[100]; + dp_value.getString(buffer, sizeof(buffer)); + ESP_LOGI(TAG, "trying to update datapoint %s value: %s", get_component_name().c_str(), buffer); +#endif + + dp_value_outstanding = dp_value; + if (optolink_->write_value(datapoint_, dp_value_outstanding)) { + is_dp_value_writing_outstanding = false; + } else { + ESP_LOGW(TAG, "write request for %s rejected due to outstanding running request - increase update_interval!", + get_component_name().c_str()); + is_dp_value_writing_outstanding = true; + } + } +} + +void DatapointComponent::write_datapoint_value(float value) { + if (div_ratio_ > 1) { + datapoint_write_request(DPValue(value)); + } else if (div_ratio_ == 1) { + switch (bytes_) { + case 1: + datapoint_write_request(DPValue((uint8_t) value)); + break; + case 2: + datapoint_write_request(DPValue((uint16_t) value)); + break; + case 4: + datapoint_write_request(DPValue((uint32_t) value)); + break; + default: + unfitting_value_type(); + break; + } + } else { + unfitting_value_type(); + } +} + +void DatapointComponent::write_datapoint_value(uint8_t value) { + if (bytes_ == 1 && div_ratio_ == 1) { + datapoint_write_request(DPValue(value)); + } else { + unfitting_value_type(); + } +} + +void DatapointComponent::write_datapoint_value(uint16_t value) { + if (bytes_ == 2 && div_ratio_ == 1) { + datapoint_write_request(DPValue(value)); + } else { + unfitting_value_type(); + } +} + +void DatapointComponent::write_datapoint_value(uint32_t value) { + if (bytes_ == 4 && div_ratio_ == 1) { + datapoint_write_request(DPValue(value)); + } else { + unfitting_value_type(); + } +} + +void DatapointComponent::write_datapoint_value(uint8_t *value, size_t length) { + if (bytes_ == length && div_ratio_ == 0) { + datapoint_write_request(DPValue(value, length)); + } else { + unfitting_value_type(); + } +} + +void DatapointComponent::unfitting_value_type() { + optolink_->set_state("Unfitting byte/div_ratio combination for sensor/component %s", get_component_name().c_str()); + ESP_LOGE(TAG, "Unfitting byte/div_ratio combination for sensor/component %s", get_component_name().c_str()); +} + +void DatapointComponent::set_optolink_state(const char *format, ...) { + va_list args; + va_start(args, format); + char buffer[128]; + std::vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + + optolink_->set_state(buffer); +} + +std::string DatapointComponent::get_optolink_state() { return optolink_->get_state(); } + +void DatapointComponent::subscribe_hass(std::string entity_id, std::function f) { + for (auto &subscription : hass_subscriptions_) { + if (subscription.entity_id == entity_id) { + subscription.callbacks.push_back(f); + return; + } + } + HassSubscription subscription{entity_id}; + subscription.callbacks.push_back(f); + hass_subscriptions_.push_back(subscription); + + api::global_api_server->subscribe_home_assistant_state( + entity_id, optional(), [this, entity_id](const std::string &state) { + ESP_LOGD(TAG, "received schedule plan from HASS entity '%s': %s", entity_id.c_str(), state.c_str()); + for (auto &subscription : hass_subscriptions_) { + if (subscription.last_state != state) { + if (subscription.entity_id == entity_id) { + subscription.last_state = state; + for (auto callback : subscription.callbacks) { + callback(state); + } + } + } + } + }); +} + +void conv2_100_F::encode(uint8_t *out, DPValue in) { + int16_t tmp = floor((in.getFloat() * 100) + 0.5); + out[1] = tmp >> 8; + out[0] = tmp & 0xFF; +} + +DPValue conv2_100_F::decode(const uint8_t *in) { + int16_t tmp = in[1] << 8 | in[0]; + DPValue out(tmp / 100.0f); + return out; +} + +void conv4_1000_F::encode(uint8_t *out, DPValue in) { + int32_t tmp = floor((in.getFloat() * 1000) + 0.5); + out[3] = tmp >> 24; + out[2] = tmp >> 16; + out[1] = tmp >> 8; + out[0] = tmp & 0xFF; +} + +DPValue conv4_1000_F::decode(const uint8_t *in) { + int32_t tmp = in[3] << 24 | in[2] << 16 | in[1] << 8 | in[0]; + DPValue out(tmp / 1000.0f); + return out; +} + +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/datapoint_component.h b/esphome/components/optolink/datapoint_component.h new file mode 100644 index 0000000000..a0548bcefa --- /dev/null +++ b/esphome/components/optolink/datapoint_component.h @@ -0,0 +1,95 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/core/log.h" +#include "esphome/core/string_ref.h" +#include "VitoWiFi.h" + +namespace esphome { +namespace optolink { + +class Optolink; + +struct HassSubscription { + std::string entity_id; + std::string last_state; + std::vector> callbacks; +}; + +class DatapointComponent { + public: + DatapointComponent(Optolink *optolink, bool writeable = false) : dp_value_outstanding((uint8_t) 0) { + optolink_ = optolink; + writeable_ = writeable; + } + + void set_address(uint32_t address) { address_ = address; } + void set_bytes(size_t bytes) { bytes_ = bytes; } + void set_writeable(bool writeable) { writeable_ = writeable; } + void set_div_ratio(size_t div_ratio) { div_ratio_ = div_ratio; } + + protected: + virtual const StringRef &get_component_name() = 0; + + uint32_t get_address() { return address_; } + + void setup_datapoint(); + + void datapoint_read_request(); + + virtual void datapoint_value_changed(float value); + virtual void datapoint_value_changed(uint8_t value); + virtual void datapoint_value_changed(uint16_t value); + virtual void datapoint_value_changed(uint32_t value); + virtual void datapoint_value_changed(std::string value); + virtual void datapoint_value_changed(uint8_t *value, size_t length); + + void write_datapoint_value(float value); + void write_datapoint_value(uint8_t value); + void write_datapoint_value(uint16_t value); + void write_datapoint_value(uint32_t value); + void write_datapoint_value(uint8_t *value, size_t length); + + void unfitting_value_type(); + void set_optolink_state(const char *format, ...); + std::string get_optolink_state(); + + void subscribe_hass(std::string entity_id, std::function f); + + private: + const size_t MAX_RETRIES_UNTIL_RESET = 10; + Optolink *optolink_; + IDatapoint *datapoint_ = nullptr; + size_t read_retries_ = 0; + size_t div_ratio_ = 0; + size_t bytes_; + uint32_t address_; + bool writeable_; + + bool is_dp_value_writing_outstanding = false; + DPValue dp_value_outstanding; + + void datapoint_write_request(DPValue dp_value); +}; + +// NOLINTNEXTLINE +class conv2_100_F : public DPType { + public: + void encode(uint8_t *out, DPValue in); + DPValue decode(const uint8_t *in); + size_t get_length() const { return 2; } +}; + +// NOLINTNEXTLINE +class conv4_1000_F : public DPType { + public: + void encode(uint8_t *out, DPValue in); + DPValue decode(const uint8_t *in); + const size_t getLength() const { return 4; } +}; + +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/number.py b/esphome/components/optolink/number/__init__.py similarity index 67% rename from esphome/components/optolink/number.py rename to esphome/components/optolink/number/__init__.py index 2f4802cd9b..34ef7ef428 100644 --- a/esphome/components/optolink/number.py +++ b/esphome/components/optolink/number/__init__.py @@ -1,4 +1,3 @@ -from esphome import core import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import number @@ -10,29 +9,27 @@ from esphome.const import ( CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_STEP, - CONF_UPDATE_INTERVAL, ) -from .sensor import SENSOR_BASE_SCHEMA -from . import OptolinkComponent, optolink_ns, CONF_OPTOLINK_ID +from .. import optolink_ns, CONF_OPTOLINK_ID, SENSOR_BASE_SCHEMA + + +DEPENDENCIES = ["optolink"] +CODEOWNERS = ["@j0ta29"] OptolinkNumber = optolink_ns.class_( "OptolinkNumber", number.Number, cg.PollingComponent ) + CONFIG_SCHEMA = ( number.NUMBER_SCHEMA.extend( { - cv.GenerateID(CONF_OPTOLINK_ID): cv.use_id(OptolinkComponent), cv.GenerateID(): cv.declare_id(OptolinkNumber), cv.Required(CONF_MAX_VALUE): cv.float_, - cv.Required(CONF_MIN_VALUE): cv.float_range(min=0.0), + cv.Required(CONF_MIN_VALUE): cv.float_range(), cv.Required(CONF_STEP): cv.float_, - cv.Optional(CONF_UPDATE_INTERVAL, default="10s"): cv.All( - cv.positive_time_period_milliseconds, - cv.Range( - min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800) - ), - ), + cv.Required(CONF_ADDRESS): cv.hex_uint32_t, + cv.Required(CONF_BYTES): cv.one_of(1, 2, 4, int=True), } ) .extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/optolink/number/optolink_number.cpp b/esphome/components/optolink/number/optolink_number.cpp new file mode 100644 index 0000000000..5c3fc87fff --- /dev/null +++ b/esphome/components/optolink/number/optolink_number.cpp @@ -0,0 +1,49 @@ +#ifdef USE_ARDUINO + +#include "optolink_number.h" +#include "../optolink.h" + +namespace esphome { +namespace optolink { + +static const char *const TAG = "optolink.number"; + +void OptolinkNumber::control(float value) { + if (value > traits.get_max_value() || value < traits.get_min_value()) { + set_optolink_state("datapoint value of number %s not in allowed range", get_component_name().c_str()); + ESP_LOGE(TAG, "datapoint value of number %s not in allowed range", get_component_name().c_str()); + } else { + ESP_LOGI(TAG, "control of number %s to value %f", get_component_name().c_str(), value); + write_datapoint_value(value); + publish_state(value); + } +}; + +void OptolinkNumber::datapoint_value_changed(uint8_t state) { + if (traits.get_min_value() >= 0) { + publish_state(state); + } else { + publish_state((int8_t) state); + } +}; + +void OptolinkNumber::datapoint_value_changed(uint16_t state) { + if (traits.get_min_value() >= 0) { + publish_state(state); + } else { + publish_state((int16_t) state); + } +}; + +void OptolinkNumber::datapoint_value_changed(uint32_t state) { + if (traits.get_min_value() >= 0) { + publish_state(state); + } else { + publish_state((int32_t) state); + } +}; + +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/number/optolink_number.h b/esphome/components/optolink/number/optolink_number.h new file mode 100644 index 0000000000..d7d3062a31 --- /dev/null +++ b/esphome/components/optolink/number/optolink_number.h @@ -0,0 +1,31 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/number/number.h" +#include "../optolink.h" +#include "../datapoint_component.h" + +namespace esphome { +namespace optolink { + +class OptolinkNumber : public DatapointComponent, public esphome::number::Number, public esphome::PollingComponent { + public: + OptolinkNumber(Optolink *optolink) : DatapointComponent(optolink, true) {} + + protected: + void setup() override { setup_datapoint(); } + void update() override { datapoint_read_request(); } + void control(float value) override; + + const StringRef &get_component_name() override { return get_name(); } + void datapoint_value_changed(float state) override { publish_state(state); }; + void datapoint_value_changed(uint8_t state) override; + void datapoint_value_changed(uint16_t state) override; + void datapoint_value_changed(uint32_t state) override; +}; + +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/optolink.cpp b/esphome/components/optolink/optolink.cpp index e5fe87d5d2..e46ebe9505 100644 --- a/esphome/components/optolink/optolink.cpp +++ b/esphome/components/optolink/optolink.cpp @@ -1,8 +1,8 @@ #ifdef USE_ARDUINO #include "esphome/core/defines.h" +#include "esphome/core/log.h" #include "optolink.h" -#include "VitoWiFi.h" #if defined(VITOWIFI_PROTOCOL) // NOLINTNEXTLINE @@ -15,14 +15,16 @@ VitoWiFiClass VitoWiFi; // this is not really a fallback but dedicated to namespace esphome { namespace optolink { +static const char *const TAG = "optolink"; + void Optolink::comm_() { - ESP_LOGD("Optolink", "enter _comm"); + ESP_LOGD(TAG, "enter _comm"); VitoWiFi.readAll(); - ESP_LOGD("Optolink", "exit _comm"); + ESP_LOGD(TAG, "exit _comm"); } void Optolink::setup() { - ESP_LOGI("Optolink", "setup"); + ESP_LOGI(TAG, "setup"); if (logger_enabled_) { VitoWiFi.setLogger(this); @@ -34,41 +36,56 @@ void Optolink::setup() { #elif defined(USE_ESP8266) VitoWiFi.setup(&Serial); #endif - - // set_interval("Optolink_comm", 10000, std::bind(&Optolink::_comm, this)); } -void Optolink::loop() { VitoWiFi.loop(); } +void Optolink::loop() { + // ESP_LOGD(TAG, "queue size: %d", VitoWiFi.queueSize()); + VitoWiFi.loop(); +} -void Optolink::set_error(const char *format, ...) { +void Optolink::set_state(const char *format, ...) { va_list args; va_start(args, format); char buffer[128]; std::vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); - error_ = buffer; + state_ = buffer; } -void Optolink::read_value(IDatapoint *datapoint) { +bool Optolink::read_value(IDatapoint *datapoint) { if (datapoint != nullptr) { - ESP_LOGI("Optolink", " read value of datapoint %s", datapoint->getName()); - VitoWiFi.readDatapoint(*datapoint); + ESP_LOGI(TAG, "requesting value of datapoint %s", datapoint->getName()); + if (!VitoWiFi.readDatapoint(*datapoint)) { + ESP_LOGE(TAG, "read request rejected due to queue overload - queue size: %d", VitoWiFi.queueSize()); + for (auto dp : datapoint->getCollection()) { + ESP_LOGD(TAG, "queued datapoint: %s", dp->getName()); + } + return false; + } } + return true; } -void Optolink::write_value(IDatapoint *datapoint, DPValue dp_value) { +bool Optolink::write_value(IDatapoint *datapoint, DPValue dp_value) { if (datapoint != nullptr) { char buffer[64]; dp_value.getString(buffer, sizeof(buffer)); - ESP_LOGI("Optolink", " write value %s of datapoint %s", buffer, datapoint->getName()); - VitoWiFi.writeDatapoint(*datapoint, dp_value); + ESP_LOGI(TAG, "sending value %s of datapoint %s", buffer, datapoint->getName()); + if (!VitoWiFi.writeDatapoint(*datapoint, dp_value)) { + ESP_LOGE(TAG, "write request rejected due to queue overload - queue size: %d", VitoWiFi.queueSize()); + for (auto dp : datapoint->getCollection()) { + ESP_LOGE(TAG, "queued dp: %s", dp->getName()); + } + return false; + } } + return true; } size_t Optolink::write(uint8_t ch) { if (ch == '\n') { - ESP_LOGD("VitoWifi", "%s", log_buffer_.c_str()); + ESP_LOGD(TAG, "VitoWiFi: %s", log_buffer_.c_str()); log_buffer_.clear(); } else { log_buffer_.push_back(ch); diff --git a/esphome/components/optolink/optolink.h b/esphome/components/optolink/optolink.h index 7389f60126..358f9f2309 100644 --- a/esphome/components/optolink/optolink.h +++ b/esphome/components/optolink/optolink.h @@ -3,9 +3,6 @@ #ifdef USE_ARDUINO #include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/binary_sensor/binary_sensor.h" -#include "esphome/components/text_sensor/text_sensor.h" #include "VitoWiFi.h" namespace esphome { @@ -13,7 +10,7 @@ namespace optolink { class Optolink : public esphome::Component, public Print { protected: - std::string error_ = "OK"; + std::string state_ = "OK"; std::string log_buffer_; bool logger_enabled_ = false; int rx_pin_; @@ -32,11 +29,11 @@ class Optolink : public esphome::Component, public Print { void set_rx_pin(int rx_pin) { rx_pin_ = rx_pin; } void set_tx_pin(int tx_pin) { tx_pin_ = tx_pin; } - void write_value(IDatapoint *datapoint, DPValue dp_value); - void read_value(IDatapoint *datapoint); + bool write_value(IDatapoint *datapoint, DPValue dp_value); + bool read_value(IDatapoint *datapoint); - void set_error(const char *format, ...); - std::string get_error() { return error_; } + void set_state(const char *format, ...); + std::string get_state() { return state_; } }; } // namespace optolink diff --git a/esphome/components/optolink/optolink_binary_sensor.h b/esphome/components/optolink/optolink_binary_sensor.h deleted file mode 100644 index 6ec63cc855..0000000000 --- a/esphome/components/optolink/optolink_binary_sensor.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#ifdef USE_ARDUINO - -#include "esphome/components/binary_sensor/binary_sensor.h" -#include "optolink.h" -#include "optolink_sensor_base.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -class OptolinkBinarySensor : public OptolinkSensorBase, - public esphome::binary_sensor::BinarySensor, - public esphome::PollingComponent { - public: - OptolinkBinarySensor(Optolink *optolink) : OptolinkSensorBase(optolink) { - bytes_ = 1; - div_ratio_ = 1; - } - - protected: - void setup() override { setup_datapoint_(); } - void update() override { optolink_->read_value(datapoint_); } - - const StringRef &get_sensor_name() override { return get_name(); } - void value_changed(float state) override { publish_state(state); }; -}; -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_device_info_sensor.cpp b/esphome/components/optolink/optolink_device_info_sensor.cpp deleted file mode 100644 index 4ce4e19974..0000000000 --- a/esphome/components/optolink/optolink_device_info_sensor.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifdef USE_ARDUINO - -#include "esphome/core/log.h" -#include "optolink_device_info_sensor.h" - -namespace esphome { -namespace optolink { - -void OptolinkDeviceInfoSensor::setup() { - datapoint_ = new Datapoint(get_name().c_str(), "optolink", 0x00f8, false); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { - uint32_t value = dp_value.getU32(); - ESP_LOGD("OptolinkTextSensor", "Datapoint %s - %s: %d", dp.getGroup(), dp.getName(), value); - uint8_t *bytes = (uint8_t *) &value; - uint16_t tmp = esphome::byteswap(*((uint16_t *) bytes)); - std::string geraetekennung = esphome::format_hex_pretty(&tmp, 1); - std::string hardware_revision = esphome::format_hex_pretty((uint8_t *) bytes + 2, 1); - std::string software_index = esphome::format_hex_pretty((uint8_t *) bytes + 3, 1); - publish_state("Device ID: " + geraetekennung + "|Hardware Revision: " + hardware_revision + - "|Software Index: " + software_index); - }); -} -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_device_info_sensor.h b/esphome/components/optolink/optolink_device_info_sensor.h deleted file mode 100644 index 94ace35a25..0000000000 --- a/esphome/components/optolink/optolink_device_info_sensor.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#ifdef USE_ARDUINO - -#include "esphome/components/text_sensor/text_sensor.h" -#include "optolink.h" -#include "optolink_sensor_base.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -class OptolinkDeviceInfoSensor : public esphome::text_sensor::TextSensor, public esphome::PollingComponent { - public: - OptolinkDeviceInfoSensor(const std::string &name, Optolink *optolink) { - optolink_ = optolink; - set_name(name.c_str()); - set_update_interval(1800000); - set_entity_category(esphome::ENTITY_CATEGORY_DIAGNOSTIC); - } - - protected: - void setup() override; - void update() override { optolink_->read_value(datapoint_); } - - private: - Optolink *optolink_; - IDatapoint *datapoint_; -}; - -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_number.cpp b/esphome/components/optolink/optolink_number.cpp deleted file mode 100644 index 3039d219fb..0000000000 --- a/esphome/components/optolink/optolink_number.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifdef USE_ARDUINO - -#include "optolink_number.h" -#include "optolink.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -void OptolinkNumber::control(float value) { - if (value > traits.get_max_value() || value < traits.get_min_value()) { - optolink_->set_error("datapoint value of number %s not in allowed range", get_sensor_name().c_str()); - ESP_LOGE("OptolinkNumber", "datapoint value of number %s not in allowed range", get_sensor_name().c_str()); - } else { - ESP_LOGI("OptolinkNumber", "control of number %s to value %f", get_sensor_name().c_str(), value); - update_datapoint_(value); - publish_state(value); - } -}; - -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_number.h b/esphome/components/optolink/optolink_number.h deleted file mode 100644 index 6b8409d539..0000000000 --- a/esphome/components/optolink/optolink_number.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#ifdef USE_ARDUINO - -#include "esphome/components/number/number.h" -#include "optolink_sensor_base.h" -#include "optolink.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -class OptolinkNumber : public OptolinkSensorBase, public esphome::number::Number, public esphome::PollingComponent { - public: - OptolinkNumber(Optolink *optolink) : OptolinkSensorBase(optolink, true) {} - - protected: - void setup() override { setup_datapoint_(); } - void update() override { optolink_->read_value(datapoint_); } - - const StringRef &get_sensor_name() override { return get_name(); } - void value_changed(float state) override { publish_state(state); }; - - void control(float value) override; -}; - -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_select.cpp b/esphome/components/optolink/optolink_select.cpp deleted file mode 100644 index 783c614bce..0000000000 --- a/esphome/components/optolink/optolink_select.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifdef USE_ARDUINO - -#include "optolink_select.h" -#include "optolink.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -void OptolinkSelect::control(const std::string &value) { - for (auto it = mapping_->begin(); it != mapping_->end(); ++it) { - if (it->second == value) { - ESP_LOGI("OptolinkSelect", "control of select %s to value %s", get_sensor_name().c_str(), it->first.c_str()); - update_datapoint_(std::stof(it->first)); - publish_state(it->second); - break; - } - if (it == mapping_->end()) { - optolink_->set_error("unknown value %s of select %s", value.c_str(), get_sensor_name().c_str()); - ESP_LOGE("OptolinkSelect", "unknown value %s of select %s", value.c_str(), get_sensor_name().c_str()); - } - } -}; - -void OptolinkSelect::value_changed(float state) { - std::string key; - if (div_ratio_ == 1) { - key = std::to_string((int) state); - } else { - key = std::to_string(state); - } - auto pos = mapping_->find(key); - if (pos == mapping_->end()) { - optolink_->set_error("value %s not found in select %s", key.c_str(), get_sensor_name().c_str()); - ESP_LOGE("OptolinkSelect", "value %s not found in select %s", key.c_str(), get_sensor_name().c_str()); - } else { - publish_state(pos->second); - } - //-----------------------------------------------publish_state(state); -}; - -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_select.h b/esphome/components/optolink/optolink_select.h deleted file mode 100644 index 995c9923f3..0000000000 --- a/esphome/components/optolink/optolink_select.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#ifdef USE_ARDUINO - -#include -#include "esphome/components/select/select.h" -#include "optolink.h" -#include "optolink_sensor_base.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -class OptolinkSelect : public OptolinkSensorBase, public esphome::select::Select, public esphome::PollingComponent { - public: - OptolinkSelect(Optolink *optolink) : OptolinkSensorBase(optolink, true) {} - - void set_map(std::map *mapping) { - mapping_ = mapping; - std::vector values; - for (auto &it : *mapping) { - values.push_back(it.second); - } - traits.set_options(values); - }; - - protected: - void setup() override { setup_datapoint_(); } - void update() override { optolink_->read_value(datapoint_); } - - const StringRef &get_sensor_name() override { return get_name(); } - void value_changed(float state) override; - - void control(const std::string &value) override; - - private: - std::map *mapping_ = nullptr; -}; - -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_sensor.h b/esphome/components/optolink/optolink_sensor.h deleted file mode 100644 index 032af7a03e..0000000000 --- a/esphome/components/optolink/optolink_sensor.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#ifdef USE_ARDUINO - -#include "esphome/components/sensor/sensor.h" -#include "optolink.h" -#include "optolink_sensor_base.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -class OptolinkSensor : public OptolinkSensorBase, public esphome::sensor::Sensor, public esphome::PollingComponent { - public: - OptolinkSensor(Optolink *optolink) : OptolinkSensorBase(optolink) { - set_state_class(esphome::sensor::STATE_CLASS_MEASUREMENT); - } - - protected: - void setup() { setup_datapoint_(); } - void update() override { optolink_->read_value(datapoint_); } - - const StringRef &get_sensor_name() override { return get_name(); } - void value_changed(float state) override { publish_state(state); }; -}; -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_sensor_base.cpp b/esphome/components/optolink/optolink_sensor_base.cpp deleted file mode 100644 index abe2b48c4e..0000000000 --- a/esphome/components/optolink/optolink_sensor_base.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#ifdef USE_ARDUINO - -#include "optolink_sensor_base.h" -#include "optolink.h" - -namespace esphome { -namespace optolink { - -void OptolinkSensorBase::update_datapoint_(float value) { - if (!writeable_) { - optolink_->set_error("try to control not writable number %s", get_sensor_name().c_str()); - ESP_LOGE("OptolinkSensorBase", "try to control not writable number %s", get_sensor_name().c_str()); - } else if (datapoint_ != nullptr) { - switch (bytes_) { - case 1: - switch (div_ratio_) { - case 1: - optolink_->write_value(datapoint_, DPValue((uint8_t) value)); - break; - case 10: - optolink_->write_value(datapoint_, DPValue((float) value)); - break; - default: - optolink_->set_error("Unknown byte/div_ratio combination for number %s", get_sensor_name().c_str()); - ESP_LOGE("OptolinkSensorBase", "Unknown byte/div_ratio combination for number %s", - get_sensor_name().c_str()); - break; - } - break; - case 2: - switch (div_ratio_) { - case 1: - optolink_->write_value(datapoint_, DPValue((uint16_t) value)); - break; - case 10: - case 100: - optolink_->write_value(datapoint_, DPValue((float) value)); - break; - default: - optolink_->set_error("Unknown byte/div_ratio combination for number %s", get_sensor_name().c_str()); - ESP_LOGE("OptolinkSensorBase", "Unknown byte/div_ratio combination for number %s", - get_sensor_name().c_str()); - break; - } - break; - case 4: - switch (div_ratio_) { - case 1: - optolink_->write_value(datapoint_, DPValue((uint32_t) value)); - break; - case 3600: - optolink_->write_value(datapoint_, DPValue((float) value)); - break; - default: - optolink_->set_error("Unknown byte/div_ratio combination for number %s", get_sensor_name().c_str()); - ESP_LOGE("OptolinkSensorBase", "Unknown byte/div_ratio combination for number %s", - get_sensor_name().c_str()); - break; - } - break; - default: - optolink_->set_error("Unknown byte value for number %s", get_sensor_name().c_str()); - ESP_LOGE("OptolinkSensorBase", "Unknown byte value for number %s", get_sensor_name().c_str()); - break; - } - } -} - -void OptolinkSensorBase::setup_datapoint_() { - switch (bytes_) { - case 1: - switch (div_ratio_) { - case 1: - datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %d", dp.getGroup(), dp.getName(), dp_value.getU8()); - value_changed(dp_value.getU8()); - }); - break; - case 10: - datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dp_value.getFloat()); - value_changed(dp_value.getFloat()); - }); - break; - default: - optolink_->set_error("Unknown byte/div_ratio combination for sensor %s", get_sensor_name().c_str()); - ESP_LOGE("OptolinkSensorBase", "Unknown byte/div_ratio combination for sensor %s", get_sensor_name().c_str()); - break; - } - break; - case 2: - switch (div_ratio_) { - case 1: - datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %d", dp.getGroup(), dp.getName(), dp_value.getU16()); - value_changed(dp_value.getU16()); - }); - break; - case 10: - datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dp_value.getFloat()); - value_changed(dp_value.getFloat()); - }); - break; - case 100: - datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dp_value.getFloat()); - value_changed(dp_value.getFloat()); - }); - break; - default: - optolink_->set_error("Unknown byte/div_ratio combination for sensor %s", get_sensor_name().c_str()); - ESP_LOGE("OptolinkSensorBase", "Unknown byte/div_ratio combination for sensor %s", get_sensor_name().c_str()); - break; - } - break; - case 4: - switch (div_ratio_) { - case 1: - datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %d", dp.getGroup(), dp.getName(), dp_value.getU32()); - value_changed(dp_value.getU32()); - }); - break; - case 3600: - datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dp_value.getFloat()); - value_changed(dp_value.getFloat()); - }); - break; - default: - optolink_->set_error("Unknown byte/div_ratio combination for sensor %s", get_sensor_name().c_str()); - ESP_LOGE("OptolinkSensorBase", "Unknown byte/div_ratio combination for sensor %s", get_sensor_name().c_str()); - break; - } - break; - default: - optolink_->set_error("Unknown byte value for sensor %s", get_sensor_name().c_str()); - ESP_LOGE("OptolinkSensorBase", "Unknown byte value for sensor %s", get_sensor_name().c_str()); - break; - } -} - -void conv2_100_F::encode(uint8_t *out, DPValue in) { - int16_t tmp = floor((in.getFloat() * 100) + 0.5); - out[1] = tmp >> 8; - out[0] = tmp & 0xFF; -} - -DPValue conv2_100_F::decode(const uint8_t *in) { - int16_t tmp = in[1] << 8 | in[0]; - DPValue out(tmp / 100.0f); - return out; -} - -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_sensor_base.h b/esphome/components/optolink/optolink_sensor_base.h deleted file mode 100644 index 3c84de4d85..0000000000 --- a/esphome/components/optolink/optolink_sensor_base.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#ifdef USE_ARDUINO - -#include "esphome/core/log.h" -#include "esphome/core/string_ref.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -class Optolink; - -class OptolinkSensorBase { - protected: - Optolink *optolink_; - bool writeable_; - IDatapoint *datapoint_ = nullptr; - uint32_t address_; - int bytes_; - int div_ratio_ = 1; - - void setup_datapoint_(); - void update_datapoint_(float value); - - public: - OptolinkSensorBase(Optolink *optolink, bool writeable = false) { - optolink_ = optolink; - writeable_ = writeable; - } - - void set_address(uint32_t address) { address_ = address; } - void set_bytes(int bytes) { bytes_ = bytes; } - void set_div_ratio(int div_ratio) { div_ratio_ = div_ratio; } - - protected: - virtual const StringRef &get_sensor_name() = 0; - virtual void value_changed(float state) = 0; -}; - -// NOLINTNEXTLINE -class conv2_100_F : public DPType { - public: - void encode(uint8_t *out, DPValue in); - DPValue decode(const uint8_t *in); - size_t get_length() const { return 2; } -}; - -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_state_sensor.h b/esphome/components/optolink/optolink_state_sensor.h deleted file mode 100644 index 9652bf2c61..0000000000 --- a/esphome/components/optolink/optolink_state_sensor.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#ifdef USE_ARDUINO - -#include "esphome/components/text_sensor/text_sensor.h" -#include "optolink.h" -#include "optolink_sensor_base.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -class OptolinkStateSensor : public esphome::text_sensor::TextSensor, public esphome::PollingComponent { - public: - OptolinkStateSensor(std::string name, Optolink *optolink) { - optolink_ = optolink; - set_name(name.c_str()); - set_update_interval(1000); - set_entity_category(esphome::ENTITY_CATEGORY_DIAGNOSTIC); - } - - protected: - void setup() override{}; - void update() override { publish_state(optolink_->get_error()); } - - private: - Optolink *optolink_; -}; -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_switch.cpp b/esphome/components/optolink/optolink_switch.cpp deleted file mode 100644 index 066f11a1a4..0000000000 --- a/esphome/components/optolink/optolink_switch.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifdef USE_ARDUINO - -#include "optolink_switch.h" -#include "optolink.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -void OptolinkSwitch::write_state(bool value) { - if (value != 0 && value != 1) { - optolink_->set_error("datapoint value of switch %s not 0 or 1", get_sensor_name().c_str()); - ESP_LOGE("OptolinkSwitch", "datapoint value of switch %s not 0 or 1", get_sensor_name().c_str()); - } else { - ESP_LOGI("OptolinkSwitch", "control of switch %s to value %d", get_sensor_name().c_str(), value); - update_datapoint_(value); - publish_state(value); - } -}; - -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_switch.h b/esphome/components/optolink/optolink_switch.h deleted file mode 100644 index 970e7a1a67..0000000000 --- a/esphome/components/optolink/optolink_switch.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#ifdef USE_ARDUINO - -#include "esphome/components/switch/switch.h" -#include "optolink_sensor_base.h" -#include "optolink.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -class OptolinkSwitch : public OptolinkSensorBase, public esphome::switch_::Switch, public esphome::PollingComponent { - public: - OptolinkSwitch(Optolink *optolink) : OptolinkSensorBase(optolink, true) { - bytes_ = 1; - div_ratio_ = 1; - } - - protected: - void setup() override { setup_datapoint_(); } - void update() override { optolink_->read_value(datapoint_); } - - const StringRef &get_sensor_name() override { return get_name(); } - void value_changed(float state) override { publish_state(state); }; - - void write_state(bool value) override; -}; - -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_text_sensor.cpp b/esphome/components/optolink/optolink_text_sensor.cpp deleted file mode 100644 index 700e9578f7..0000000000 --- a/esphome/components/optolink/optolink_text_sensor.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifdef USE_ARDUINO - -#include "optolink_text_sensor.h" -#include "optolink.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -void OptolinkTextSensor::setup() { - if (!raw_) { - setup_datapoint_(); - } else { - datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); - datapoint_->setLength(bytes_); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: ", dp.getGroup(), dp.getName()); - uint8_t buffer[bytes_ + 1]; - dp_value.getRaw(buffer); - buffer[bytes_] = 0x0; - publish_state((char *) buffer); - }); - } -}; - -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/optolink_text_sensor.h b/esphome/components/optolink/optolink_text_sensor.h deleted file mode 100644 index 950b68f1cb..0000000000 --- a/esphome/components/optolink/optolink_text_sensor.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#ifdef USE_ARDUINO - -#include "esphome/components/text_sensor/text_sensor.h" -#include "optolink.h" -#include "optolink_sensor_base.h" -#include "VitoWiFi.h" - -namespace esphome { -namespace optolink { - -class OptolinkTextSensor : public OptolinkSensorBase, - public esphome::text_sensor::TextSensor, - public esphome::PollingComponent { - public: - OptolinkTextSensor(Optolink *optolink) : OptolinkSensorBase(optolink) {} - - void set_raw(bool raw) { raw_ = raw; } - - protected: - void setup() override; - void update() override { optolink_->read_value(datapoint_); } - - const StringRef &get_sensor_name() override { return get_name(); } - void value_changed(float state) override { publish_state(std::to_string(state)); }; - - private: - bool raw_ = false; -}; - -} // namespace optolink -} // namespace esphome - -#endif diff --git a/esphome/components/optolink/select.py b/esphome/components/optolink/select/__init__.py similarity index 78% rename from esphome/components/optolink/select.py rename to esphome/components/optolink/select/__init__.py index 5d6aa2ff85..b1f846c4fd 100644 --- a/esphome/components/optolink/select.py +++ b/esphome/components/optolink/select/__init__.py @@ -1,4 +1,3 @@ -from esphome import core import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import select @@ -9,10 +8,12 @@ from esphome.const import ( CONF_FROM, CONF_ID, CONF_TO, - CONF_UPDATE_INTERVAL, ) -from . import OptolinkComponent, optolink_ns, CONF_OPTOLINK_ID -from .sensor import SENSOR_BASE_SCHEMA +from .. import optolink_ns, CONF_OPTOLINK_ID, SENSOR_BASE_SCHEMA + +DEPENDENCIES = ["optolink"] +CODEOWNERS = ["@j0ta29"] + OptolinkSelect = optolink_ns.class_( "OptolinkSelect", select.Select, cg.PollingComponent @@ -37,18 +38,13 @@ MAP_ID = "mappings" CONFIG_SCHEMA = ( select.SELECT_SCHEMA.extend( { - cv.GenerateID(CONF_OPTOLINK_ID): cv.use_id(OptolinkComponent), cv.GenerateID(): cv.declare_id(OptolinkSelect), cv.GenerateID(MAP_ID): cv.declare_id( cg.std_ns.class_("map").template(cg.std_string, cg.std_string) ), cv.Required(CONF_MAP): cv.ensure_list(validate_mapping), - cv.Optional(CONF_UPDATE_INTERVAL, default="10s"): cv.All( - cv.positive_time_period_milliseconds, - cv.Range( - min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800) - ), - ), + cv.Required(CONF_ADDRESS): cv.hex_uint32_t, + cv.Required(CONF_BYTES): cv.one_of(1, 2, 4, int=True), } ) .extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/optolink/select/optolink_select.cpp b/esphome/components/optolink/select/optolink_select.cpp new file mode 100644 index 0000000000..0f21c6d0c6 --- /dev/null +++ b/esphome/components/optolink/select/optolink_select.cpp @@ -0,0 +1,59 @@ +#ifdef USE_ARDUINO + +#include "optolink_select.h" +#include "../optolink.h" + +namespace esphome { +namespace optolink { + +static const char *const TAG = "optolink.select"; + +void OptolinkSelect::control(const std::string &value) { + for (auto it = mapping_->begin(); it != mapping_->end(); ++it) { + if (it->second == value) { + ESP_LOGI(TAG, "control of select %s to value %s", get_component_name().c_str(), it->first.c_str()); + write_datapoint_value(std::stof(it->first)); + publish_state(it->second); + break; + } + if (it == mapping_->end()) { + set_optolink_state("unknown value %s of select %s", value.c_str(), get_component_name().c_str()); + ESP_LOGE(TAG, "unknown value %s of select %s", value.c_str(), get_component_name().c_str()); + } + } +}; + +void OptolinkSelect::datapoint_value_changed(std::string key) { + auto pos = mapping_->find(key); + if (pos == mapping_->end()) { + set_optolink_state("value %s not found in select %s", key.c_str(), get_component_name().c_str()); + ESP_LOGE(TAG, "value %s not found in select %s", key.c_str(), get_component_name().c_str()); + } else { + publish_state(pos->second); + } +} + +void OptolinkSelect::datapoint_value_changed(uint8_t state) { + std::string key = std::to_string(state); + datapoint_value_changed(key); +} + +void OptolinkSelect::datapoint_value_changed(uint16_t state) { + std::string key = std::to_string(state); + datapoint_value_changed(key); +} + +void OptolinkSelect::datapoint_value_changed(uint32_t state) { + std::string key = std::to_string(state); + datapoint_value_changed(key); +} + +void OptolinkSelect::datapoint_value_changed(float state) { + std::string key = std::to_string(state); + datapoint_value_changed(key); +} + +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/select/optolink_select.h b/esphome/components/optolink/select/optolink_select.h new file mode 100644 index 0000000000..e578a858b1 --- /dev/null +++ b/esphome/components/optolink/select/optolink_select.h @@ -0,0 +1,45 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/select/select.h" +#include "../optolink.h" +#include "../datapoint_component.h" +#include + +namespace esphome { +namespace optolink { + +class OptolinkSelect : public DatapointComponent, public esphome::select::Select, public esphome::PollingComponent { + public: + OptolinkSelect(Optolink *optolink) : DatapointComponent(optolink, true) {} + + void set_map(std::map *mapping) { + mapping_ = mapping; + std::vector values; + for (auto &it : *mapping) { + values.push_back(it.second); + } + traits.set_options(values); + }; + + protected: + void setup() override { setup_datapoint(); } + void update() override { datapoint_read_request(); } + void control(const std::string &value) override; + + const StringRef &get_component_name() override { return get_name(); } + void datapoint_value_changed(std::string state) override; + void datapoint_value_changed(uint8_t state) override; + void datapoint_value_changed(uint16_t state) override; + void datapoint_value_changed(uint32_t state) override; + void datapoint_value_changed(float state) override; + + private: + std::map *mapping_ = nullptr; +}; + +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/sensor.py b/esphome/components/optolink/sensor.py deleted file mode 100644 index 4991ac110d..0000000000 --- a/esphome/components/optolink/sensor.py +++ /dev/null @@ -1,51 +0,0 @@ -from esphome import core -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import sensor -from esphome.const import ( - CONF_ID, - CONF_ADDRESS, - CONF_BYTES, - CONF_DIV_RATIO, - CONF_UPDATE_INTERVAL, -) -from . import optolink_ns, OptolinkComponent - -OptolinkSensor = optolink_ns.class_( - "OptolinkSensor", sensor.Sensor, cg.PollingComponent -) -CONF_OPTOLINK_ID = "optolink_id" -SENSOR_BASE_SCHEMA = cv.Schema( - { - cv.Required(CONF_ADDRESS): cv.hex_uint32_t, - cv.Required(CONF_BYTES): cv.one_of(1, 2, 4, int=True), - cv.Optional(CONF_DIV_RATIO, default=1): cv.one_of(1, 10, 100, 3600, int=True), - } -) -CONFIG_SCHEMA = ( - sensor.sensor_schema(OptolinkSensor) - .extend( - { - cv.GenerateID(CONF_OPTOLINK_ID): cv.use_id(OptolinkComponent), - cv.Optional(CONF_UPDATE_INTERVAL, default="10s"): cv.All( - cv.positive_time_period_milliseconds, - cv.Range( - min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800) - ), - ), - } - ) - .extend(SENSOR_BASE_SCHEMA) -) - - -async def to_code(config): - component = await cg.get_variable(config[CONF_OPTOLINK_ID]) - var = cg.new_Pvariable(config[CONF_ID], component) - - await cg.register_component(var, config) - await sensor.register_sensor(var, config) - - cg.add(var.set_address(config[CONF_ADDRESS])) - cg.add(var.set_bytes(config[CONF_BYTES])) - cg.add(var.set_div_ratio(config[CONF_DIV_RATIO])) diff --git a/esphome/components/optolink/sensor/__init__.py b/esphome/components/optolink/sensor/__init__.py new file mode 100644 index 0000000000..4c91a4666f --- /dev/null +++ b/esphome/components/optolink/sensor/__init__.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_ADDRESS, + CONF_BYTES, + CONF_DIV_RATIO, + CONF_ID, + CONF_MIN_VALUE, +) +from .. import CONF_OPTOLINK_ID, SENSOR_BASE_SCHEMA, optolink_ns + +DEPENDENCIES = ["optolink"] +CODEOWNERS = ["@j0ta29"] + + +OptolinkSensor = optolink_ns.class_( + "OptolinkSensor", sensor.Sensor, cg.PollingComponent +) +CONFIG_SCHEMA = ( + sensor.SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(OptolinkSensor), + cv.Required(CONF_ADDRESS): cv.hex_uint32_t, + cv.Required(CONF_BYTES): cv.one_of(1, 2, 4, int=True), + cv.Optional(CONF_MIN_VALUE): cv.float_, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(SENSOR_BASE_SCHEMA) +) + + +async def to_code(config): + component = await cg.get_variable(config[CONF_OPTOLINK_ID]) + var = cg.new_Pvariable(config[CONF_ID], component) + + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + cg.add(var.set_address(config[CONF_ADDRESS])) + cg.add(var.set_bytes(config[CONF_BYTES])) + cg.add(var.set_div_ratio(config[CONF_DIV_RATIO])) + if CONF_MIN_VALUE in config: + cg.add(var.set_min_value(config[CONF_MIN_VALUE])) diff --git a/esphome/components/optolink/sensor/optolink_sensor.cpp b/esphome/components/optolink/sensor/optolink_sensor.cpp new file mode 100644 index 0000000000..a1c2df7815 --- /dev/null +++ b/esphome/components/optolink/sensor/optolink_sensor.cpp @@ -0,0 +1,40 @@ +#ifdef USE_ARDUINO + +#include "optolink_sensor.h" +#include "../optolink.h" + +namespace esphome { +namespace optolink { + +static const char *const TAG = "optolink.sensor"; + +void OptolinkSensor::set_min_value(float min_value) { min_value_ = -29.3; } + +void OptolinkSensor::datapoint_value_changed(uint8_t state) { + if (min_value_ >= 0.0) { + publish_state(state); + } else { + publish_state((int8_t) state); + } +}; + +void OptolinkSensor::datapoint_value_changed(uint16_t state) { + if (min_value_ >= 0.0) { + publish_state(state); + } else { + publish_state((int16_t) state); + } +} + +void OptolinkSensor::datapoint_value_changed(uint32_t state) { + if (min_value_ >= 0.0) { + publish_state(state); + } else { + publish_state((int32_t) state); + } +}; + +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/sensor/optolink_sensor.h b/esphome/components/optolink/sensor/optolink_sensor.h new file mode 100644 index 0000000000..59e9d4bdb7 --- /dev/null +++ b/esphome/components/optolink/sensor/optolink_sensor.h @@ -0,0 +1,37 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/sensor/sensor.h" +#include "../optolink.h" +#include "../datapoint_component.h" +#include + +namespace esphome { +namespace optolink { + +class OptolinkSensor : public DatapointComponent, public esphome::sensor::Sensor, public esphome::PollingComponent { + public: + OptolinkSensor(Optolink *optolink) : DatapointComponent(optolink) { + set_state_class(esphome::sensor::STATE_CLASS_MEASUREMENT); + } + + void set_min_value(float min_value); + + protected: + void setup() { setup_datapoint(); } + void update() override { datapoint_read_request(); } + + const StringRef &get_component_name() override { return get_name(); } + void datapoint_value_changed(float state) override { publish_state(state); }; + void datapoint_value_changed(uint8_t state) override; + void datapoint_value_changed(uint16_t state) override; + void datapoint_value_changed(uint32_t state) override; + + private: + float min_value_ = -FLT_MAX; +}; +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/switch.py b/esphome/components/optolink/switch.py deleted file mode 100644 index 32540cdf0f..0000000000 --- a/esphome/components/optolink/switch.py +++ /dev/null @@ -1,33 +0,0 @@ -from esphome import core -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import switch -from esphome.const import CONF_ADDRESS, CONF_ID, CONF_UPDATE_INTERVAL -from . import OptolinkComponent, optolink_ns - -OptolinkSwitch = optolink_ns.class_( - "OptolinkSwitch", switch.Switch, cg.PollingComponent -) - -CONF_OPTOLINK_ID = "optolink_id" -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(CONF_OPTOLINK_ID): cv.use_id(OptolinkComponent), - cv.GenerateID(): cv.declare_id(OptolinkSwitch), - cv.Required(CONF_ADDRESS): cv.hex_uint32_t, - cv.Optional(CONF_UPDATE_INTERVAL, default="10s"): cv.All( - cv.positive_time_period_milliseconds, - cv.Range(min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800)), - ), - } -).extend(cv.COMPONENT_SCHEMA) - - -async def to_code(config): - component = await cg.get_variable(config[CONF_OPTOLINK_ID]) - var = cg.new_Pvariable(config[CONF_ID], component) - - await cg.register_component(var, config) - await switch.register_switch(var, config) - - cg.add(var.set_address(config[CONF_ADDRESS])) diff --git a/esphome/components/optolink/switch/__init__.py b/esphome/components/optolink/switch/__init__.py new file mode 100644 index 0000000000..0d47229ca5 --- /dev/null +++ b/esphome/components/optolink/switch/__init__.py @@ -0,0 +1,42 @@ +from esphome import core +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_UPDATE_INTERVAL +from .. import SENSOR_BASE_SCHEMA, optolink_ns + +DEPENDENCIES = ["optolink"] +CODEOWNERS = ["@j0ta29"] + + +OptolinkSwitch = optolink_ns.class_( + "OptolinkSwitch", switch.Switch, cg.PollingComponent +) + +CONF_OPTOLINK_ID = "optolink_id" +CONFIG_SCHEMA = ( + switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(OptolinkSwitch), + cv.Required(CONF_ADDRESS): cv.hex_uint32_t, + cv.Optional(CONF_UPDATE_INTERVAL, default="10s"): cv.All( + cv.positive_time_period_milliseconds, + cv.Range( + min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800) + ), + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(SENSOR_BASE_SCHEMA) +) + + +async def to_code(config): + component = await cg.get_variable(config[CONF_OPTOLINK_ID]) + var = cg.new_Pvariable(config[CONF_ID], component) + + await cg.register_component(var, config) + await switch.register_switch(var, config) + + cg.add(var.set_address(config[CONF_ADDRESS])) diff --git a/esphome/components/optolink/switch/optolink_switch.cpp b/esphome/components/optolink/switch/optolink_switch.cpp new file mode 100644 index 0000000000..cfd14e287f --- /dev/null +++ b/esphome/components/optolink/switch/optolink_switch.cpp @@ -0,0 +1,25 @@ +#ifdef USE_ARDUINO + +#include "optolink_switch.h" +#include "../optolink.h" + +namespace esphome { +namespace optolink { + +static const char *const TAG = "optolink.switch"; + +void OptolinkSwitch::write_state(bool value) { + if (value != 0 && value != 1) { + set_optolink_state("datapoint value of switch %s not 0 or 1", get_component_name().c_str()); + ESP_LOGE(TAG, "datapoint value of switch %s not 0 or 1", get_component_name().c_str()); + } else { + ESP_LOGI(TAG, "control of switch %s to value %d", get_component_name().c_str(), value); + write_datapoint_value((uint8_t) value); + publish_state(value); + } +}; + +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/switch/optolink_switch.h b/esphome/components/optolink/switch/optolink_switch.h new file mode 100644 index 0000000000..75cbe85c36 --- /dev/null +++ b/esphome/components/optolink/switch/optolink_switch.h @@ -0,0 +1,31 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/switch/switch.h" +#include "../optolink.h" +#include "../datapoint_component.h" + +namespace esphome { +namespace optolink { + +class OptolinkSwitch : public DatapointComponent, public esphome::switch_::Switch, public esphome::PollingComponent { + public: + OptolinkSwitch(Optolink *optolink) : DatapointComponent(optolink, true) { + set_bytes(1); + set_div_ratio(1); + } + + protected: + void setup() override { setup_datapoint(); } + void update() override { datapoint_read_request(); } + void write_state(bool value) override; + + const StringRef &get_component_name() override { return get_name(); } + void datapoint_value_changed(uint8_t state) override { publish_state(state); }; +}; + +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/text_sensor.py b/esphome/components/optolink/text_sensor.py deleted file mode 100644 index 09280c91aa..0000000000 --- a/esphome/components/optolink/text_sensor.py +++ /dev/null @@ -1,49 +0,0 @@ -from esphome import core -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import text_sensor -from esphome.const import ( - CONF_ADDRESS, - CONF_BYTES, - CONF_DIV_RATIO, - CONF_ID, - CONF_RAW, - CONF_UPDATE_INTERVAL, -) -from . import optolink_ns, OptolinkComponent, CONF_OPTOLINK_ID -from .sensor import SENSOR_BASE_SCHEMA - -OptolinkTextSensor = optolink_ns.class_( - "OptolinkTextSensor", text_sensor.TextSensor, cg.PollingComponent -) - -CONFIG_SCHEMA = cv.All( - text_sensor.text_sensor_schema(OptolinkTextSensor) - .extend( - { - cv.GenerateID(CONF_OPTOLINK_ID): cv.use_id(OptolinkComponent), - cv.Optional(CONF_UPDATE_INTERVAL, default="10s"): cv.All( - cv.positive_time_period_milliseconds, - cv.Range( - min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800) - ), - ), - cv.Optional(CONF_RAW, default=False): cv.boolean, - } - ) - .extend(SENSOR_BASE_SCHEMA) - .extend({cv.Required(CONF_BYTES): cv.int_}), -) - - -async def to_code(config): - component = await cg.get_variable(config[CONF_OPTOLINK_ID]) - var = cg.new_Pvariable(config[CONF_ID], component) - - await cg.register_component(var, config) - await text_sensor.register_text_sensor(var, config) - - cg.add(var.set_raw(config[CONF_RAW])) - cg.add(var.set_address(config[CONF_ADDRESS])) - cg.add(var.set_bytes(config[CONF_BYTES])) - cg.add(var.set_div_ratio(config[CONF_DIV_RATIO])) diff --git a/esphome/components/optolink/text_sensor/__init__.py b/esphome/components/optolink/text_sensor/__init__.py new file mode 100644 index 0000000000..376fdd66c8 --- /dev/null +++ b/esphome/components/optolink/text_sensor/__init__.py @@ -0,0 +1,174 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import ( + CONF_ADDRESS, + CONF_BYTES, + CONF_DIV_RATIO, + CONF_ENTITY_ID, + CONF_ID, + CONF_MODE, +) +from .. import optolink_ns, CONF_OPTOLINK_ID, SENSOR_BASE_SCHEMA + +DEPENDENCIES = ["optolink", "api"] +CODEOWNERS = ["@j0ta29"] + +TextSensorMode = optolink_ns.enum("TextSensorMode") +MODE = { + "MAP": TextSensorMode.MAP, + "RAW": TextSensorMode.RAW, + "DAY_SCHEDULE": TextSensorMode.DAY_SCHEDULE, + "DAY_SCHEDULE_SYNCHRONIZED": TextSensorMode.DAY_SCHEDULE_SYNCHRONIZED, + "DEVICE_INFO": TextSensorMode.DEVICE_INFO, + "STATE_INFO": TextSensorMode.STATE_INFO, +} +DAY_OF_WEEK = { + "MONDAY": 0, + "TUESDAY": 1, + "WEDNESDAY": 2, + "THURSDAY": 3, + "FRIDAY": 4, + "SATURDAY": 5, + "SUNDAY": 6, +} +CONF_DAY_OF_WEEK = "day_of_week" + +OptolinkTextSensor = optolink_ns.class_( + "OptolinkTextSensor", text_sensor.TextSensor, cg.PollingComponent +) + + +def check_address(): + def validator_(config): + modes_address_needed = [ + "MAP", + "RAW", + "DAY_SCHEDULE", + "DAY_SCHEDULE_SYNCHRONIZED", + ] + address_needed = config[CONF_MODE] in modes_address_needed + address_defined = CONF_ADDRESS in config + if address_needed and not address_defined: + raise cv.Invalid( + f"{CONF_ADDRESS} is required in this modes: {modes_address_needed}" + ) + if not address_needed and address_defined: + raise cv.Invalid( + f"{CONF_ADDRESS} is only allowed in this modes mode: {modes_address_needed}" + ) + return config + + return validator_ + + +def check_bytes(): + def validator_(config): + modes_bytes_needed = ["MAP", "RAW", "DAY_SCHEDULE", "DAY_SCHEDULE_SYNCHRONIZED"] + bytes_needed = config[CONF_MODE] in modes_bytes_needed + bytes_defined = CONF_BYTES in config + if bytes_needed and not bytes_defined: + raise cv.Invalid( + f"{CONF_BYTES} is required in this modes: {modes_bytes_needed}" + ) + if not bytes_needed and bytes_defined: + raise cv.Invalid( + f"{CONF_BYTES} is only allowed in this modes: {modes_bytes_needed}" + ) + + modes_bytes_range_1_to_9 = ["MAP", "RAW"] + if config[CONF_MODE] in modes_bytes_range_1_to_9 and config[ + CONF_BYTES + ] not in range(0, 10): + raise cv.Invalid( + f"{CONF_BYTES} must be between 1 and 9 for this modes: {modes_bytes_range_1_to_9}" + ) + + modes_bytes_day_schedule = ["DAY_SCHEDULE", "DAY_SCHEDULE_SYNCHRONIZED"] + if config[CONF_MODE] in modes_bytes_day_schedule and config[CONF_BYTES] not in [ + 56 + ]: + raise cv.Invalid( + f"{CONF_BYTES} must be 56 for this modes: {modes_bytes_day_schedule}" + ) + + return config + + return validator_ + + +def check_dow(): + def validator_(config): + modes_dow_needed = ["DAY_SCHEDULE", "DAY_SCHEDULE_SYNCHRONIZED"] + if config[CONF_MODE] in modes_dow_needed and CONF_DAY_OF_WEEK not in config: + raise cv.Invalid( + f"{CONF_DAY_OF_WEEK} is required in this modes: {modes_dow_needed}" + ) + if config[CONF_MODE] not in modes_dow_needed and CONF_DAY_OF_WEEK in config: + raise cv.Invalid( + f"{CONF_DAY_OF_WEEK} is only allowed in this modes: {modes_dow_needed}" + ) + return config + + return validator_ + + +def check_entity_id(): + def validator_(config): + modes_entitiy_id_needed = ["DAY_SCHEDULE_SYNCHRONIZED"] + if ( + config[CONF_MODE] in modes_entitiy_id_needed + and CONF_ENTITY_ID not in config + ): + raise cv.Invalid( + f"{CONF_ENTITY_ID} is required in this modes: {modes_entitiy_id_needed}" + ) + if ( + config[CONF_MODE] not in modes_entitiy_id_needed + and CONF_ENTITY_ID in config + ): + raise cv.Invalid( + f"{CONF_ENTITY_ID} is only allowed in this modes: {modes_entitiy_id_needed}" + ) + return config + + return validator_ + + +CONFIG_SCHEMA = cv.All( + text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(OptolinkTextSensor), + cv.Required(CONF_MODE): cv.enum(MODE, upper=True), + cv.Optional(CONF_ADDRESS): cv.hex_uint32_t, + cv.Optional(CONF_BYTES): cv.uint8_t, + cv.Optional(CONF_DAY_OF_WEEK): cv.enum(DAY_OF_WEEK, upper=True), + cv.Optional(CONF_ENTITY_ID): cv.entity_id, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(SENSOR_BASE_SCHEMA), + check_address(), + check_bytes(), + check_dow(), + check_entity_id(), +) + + +async def to_code(config): + component = await cg.get_variable(config[CONF_OPTOLINK_ID]) + var = cg.new_Pvariable(config[CONF_ID], component) + + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + + cg.add(var.set_mode(config[CONF_MODE])) + if CONF_ADDRESS in config: + cg.add(var.set_address(config[CONF_ADDRESS])) + cg.add(var.set_div_ratio(config[CONF_DIV_RATIO])) + if CONF_BYTES in config: + cg.add(var.set_bytes(config[CONF_BYTES])) + if CONF_DAY_OF_WEEK in config: + cg.add(var.set_day_of_week(config[CONF_DAY_OF_WEEK])) + if CONF_ENTITY_ID in config: + cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) diff --git a/esphome/components/optolink/text_sensor/optolink_text_sensor.cpp b/esphome/components/optolink/text_sensor/optolink_text_sensor.cpp new file mode 100644 index 0000000000..9d1d8d1e32 --- /dev/null +++ b/esphome/components/optolink/text_sensor/optolink_text_sensor.cpp @@ -0,0 +1,176 @@ +#ifdef USE_ARDUINO + +#include "esphome/core/log.h" +#include "optolink_text_sensor.h" +#include "../optolink.h" +#include "../datapoint_component.h" + +namespace esphome { +namespace optolink { + +static const char *const TAG = "optolink.text_sensor"; + +struct Time { + int hours; + int minutes; +}; + +bool check_time_sequence(const Time &t1, const Time &t2) { + if (t2.hours > t1.hours || (t2.hours == t1.hours && t2.minutes >= t1.minutes)) { + return true; + } + return false; +} + +bool check_time_values(const Time &time) { + return (time.hours >= 0 && time.hours <= 23) && (time.minutes >= 0 && time.minutes <= 59); +} + +uint8_t *encode_time_string(std::string input) { + char buffer[49]; + strncpy(buffer, input.c_str(), sizeof(buffer)); + buffer[sizeof(buffer) - 1] = 0x00; + Time time_values[8]; + Time prev_time = {0, 0}; + int time_count = 0; + + char *token = strtok(buffer, " "); + while (token && time_count < 8) { + Time current_time; + if (sscanf(token, "%d:%d", ¤t_time.hours, ¤t_time.minutes) == 2) { + if (check_time_values(current_time) && check_time_sequence(prev_time, current_time)) { + time_values[time_count++] = current_time; + prev_time = current_time; + } else { + ESP_LOGE( + TAG, + "Time values should be in the format hh:mm and in increasing order within the range of 00:00 to 23:59"); + return 0; + } + } else { + ESP_LOGE(TAG, "Invalid time format"); + return 0; + } + token = strtok(nullptr, " "); + } + + if (time_count % 2) { + ESP_LOGE(TAG, "Number of time values must be even"); + return 0; + } + + while (time_count < 8) { + time_values[time_count++] = {31, 70}; + } + + static uint8_t data[8]; + // ESP_LOGD(TAG, "Parsed time values:"); + for (int i = 0; i < 8; i++) { + Time time = time_values[i]; + data[i] = (time.hours << 3) + (time.minutes / 10); + // ESP_LOGD(TAG, " %02d:%02d => %d", time.hours, time.minutes, data[i]); + } + + return data; +} + +void OptolinkTextSensor::setup() { + switch (mode_) { + case MAP: + break; + case RAW: + set_div_ratio(0); + break; + case DAY_SCHEDULE: + set_div_ratio(0); + set_bytes(8); + set_address(get_address() + 8 * dow_); + break; + case DAY_SCHEDULE_SYNCHRONIZED: + set_writeable(true); + set_div_ratio(0); + set_bytes(8); + set_address(get_address() + 8 * dow_); + ESP_LOGI(TAG, "subscribing to schedule plan from HASS entity '%s' for component %s", this->entity_id_.c_str(), + get_component_name().c_str()); + subscribe_hass(entity_id_, [this](const std::string &state) { + ESP_LOGD(TAG, "update for schedule plan for component %s: %s", get_component_name().c_str(), state.c_str()); + uint8_t *data = encode_time_string(state); + if (data) { + write_datapoint_value(data, 8); + } else { + ESP_LOGW(TAG, "not changing any value of datapoint %s", get_component_name().c_str()); + } + }); + break; + case DEVICE_INFO: + set_entity_category(esphome::ENTITY_CATEGORY_DIAGNOSTIC); + set_bytes(4); + set_address(0x00f8); + break; + case STATE_INFO: + set_entity_category(esphome::ENTITY_CATEGORY_DIAGNOSTIC); + return; // no datapoint setup! + } + setup_datapoint(); +}; + +void OptolinkTextSensor::update() { + if (mode_ == STATE_INFO) { + publish_state(get_optolink_state()); + } else { + datapoint_read_request(); + } +} + +void OptolinkTextSensor::datapoint_value_changed(uint8_t *value, size_t length) { + switch (mode_) { + case RAW: + publish_state(std::string((const char *) value)); + break; + case DAY_SCHEDULE: + case DAY_SCHEDULE_SYNCHRONIZED: + if (length == 8) { + char buffer[6 * length + 1]; + for (int i = 0; i < 8; i++) { + int hour = value[i] >> 3; + int minute = (value[i] & 0b111) * 10; + if (value[i] != 0xFF) { + sprintf(buffer + i * 6, "%02d:%02d ", hour, minute); + } else { + sprintf(buffer + i * 6, " "); + } + } + publish_state(buffer); + } else { + unfitting_value_type(); + } + break; + case DEVICE_INFO: + case STATE_INFO: + case MAP: + unfitting_value_type(); + break; + } +}; + +void OptolinkTextSensor::datapoint_value_changed(uint32_t value) { + switch (mode_) { + case DEVICE_INFO: { + uint8_t *bytes = (uint8_t *) &value; + uint16_t tmp = esphome::byteswap(*((uint16_t *) bytes)); + std::string geraetekennung = esphome::format_hex_pretty(&tmp, 1); + std::string hardware_revision = esphome::format_hex_pretty((uint8_t *) bytes + 2, 1); + std::string software_index = esphome::format_hex_pretty((uint8_t *) bytes + 3, 1); + publish_state("Device ID: " + geraetekennung + "|Hardware Revision: " + hardware_revision + + "|Software Index: " + software_index); + } break; + default: + publish_state(std::to_string(value)); + } +}; + +} // namespace optolink +} // namespace esphome + +#endif diff --git a/esphome/components/optolink/text_sensor/optolink_text_sensor.h b/esphome/components/optolink/text_sensor/optolink_text_sensor.h new file mode 100644 index 0000000000..1583da1d99 --- /dev/null +++ b/esphome/components/optolink/text_sensor/optolink_text_sensor.h @@ -0,0 +1,44 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/text_sensor/text_sensor.h" +#include "../optolink.h" +#include "../datapoint_component.h" + +namespace esphome { +namespace optolink { + +enum TextSensorMode { MAP, RAW, DAY_SCHEDULE, DAY_SCHEDULE_SYNCHRONIZED, DEVICE_INFO, STATE_INFO }; + +class OptolinkTextSensor : public DatapointComponent, + public esphome::text_sensor::TextSensor, + public esphome::PollingComponent { + public: + OptolinkTextSensor(Optolink *optolink) : DatapointComponent(optolink) {} + + void set_mode(TextSensorMode mode) { mode_ = mode; } + void set_day_of_week(int dow) { dow_ = dow; } + void set_entity_id(const std::string &entity_id) { entity_id_ = entity_id; } + + protected: + void setup() override; + void update() override; + + const StringRef &get_component_name() override { return get_name(); } + void datapoint_value_changed(float state) override { publish_state(std::to_string(state)); }; + void datapoint_value_changed(uint8_t state) override { publish_state(std::to_string(state)); }; + void datapoint_value_changed(uint16_t state) override { publish_state(std::to_string(state)); }; + void datapoint_value_changed(uint32_t state) override; + void datapoint_value_changed(uint8_t *state, size_t length) override; + + private: + TextSensorMode mode_ = MAP; + int dow_ = 0; + std::string entity_id_; +}; + +} // namespace optolink +} // namespace esphome + +#endif diff --git a/platformio.ini b/platformio.ini index 82a11f7cb5..ade6d231b8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -66,7 +66,7 @@ lib_deps = rweather/Crypto@0.4.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea tonia/HeatpumpIR@1.0.23 ; heatpumpir - bertmelis/VitoWiFi@1.0.2 ; optolink + bertmelis/VitoWiFi@1.1.2 ; optolink build_flags = ${common.build_flags} -DUSE_ARDUINO diff --git a/tests/test1.yaml b/tests/test1.yaml index 1d1b088dac..5c0ae4a336 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -409,8 +409,6 @@ optolink: rx_pin: GPIO15 tx_pin: GPIO16 logger: true - device_info: Device Info - state: Component state micronova: enable_rx_pin: @@ -3909,11 +3907,6 @@ text_sensor: tag_name: OPTARIF name: optarif teleinfo_id: myteleinfo - - platform: optolink - name: Error history 1 - address: 0x7590 - bytes: 9 - raw: true - platform: ld2410 version: name: "presenece sensor version"