From f1df1966872b183cc32d92adabe21b1163ab2793 Mon Sep 17 00:00:00 2001 From: j0ta29 <jota@gmx.de> Date: Sat, 26 Aug 2023 18:48:36 +0000 Subject: [PATCH] added textsensor mode --- esphome/components/optolink/__init__.py | 18 +++++ esphome/components/optolink/binary_sensor.py | 15 +--- esphome/components/optolink/number.py | 12 +-- .../optolink/optolink_text_sensor.cpp | 27 ++++++- .../optolink/optolink_text_sensor.h | 10 ++- esphome/components/optolink/select.py | 12 +-- esphome/components/optolink/sensor.py | 22 +----- esphome/components/optolink/text_sensor.py | 73 +++++++++++++++---- 8 files changed, 115 insertions(+), 74 deletions(-) diff --git a/esphome/components/optolink/__init__.py b/esphome/components/optolink/__init__.py index 49630e804b..d3e879868e 100644 --- a/esphome/components/optolink/__init__.py +++ b/esphome/components/optolink/__init__.py @@ -1,14 +1,18 @@ +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_ADDRESS, + 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 @@ -31,6 +35,20 @@ DeviceInfoSensor = optolink_ns.class_( ) DEVICE_INFO_SENSOR_ID = "device_info_sensor_id" +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.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), + } +) + def required_on_esp32(attribute): """Validate that this option can only be specified on the given target platforms.""" diff --git a/esphome/components/optolink/binary_sensor.py b/esphome/components/optolink/binary_sensor.py index f55b9c6d16..9d1495437e 100644 --- a/esphome/components/optolink/binary_sensor.py +++ b/esphome/components/optolink/binary_sensor.py @@ -1,22 +1,13 @@ -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 +from esphome.const import CONF_ADDRESS, CONF_ID +from . import SENSOR_BASE_SCHEMA, 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)), - ), - } + SENSOR_BASE_SCHEMA ) diff --git a/esphome/components/optolink/number.py b/esphome/components/optolink/number.py index 2f4802cd9b..ab9a4e8c09 100644 --- a/esphome/components/optolink/number.py +++ b/esphome/components/optolink/number.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,10 +9,9 @@ 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 OptolinkNumber = optolink_ns.class_( "OptolinkNumber", number.Number, cg.PollingComponent @@ -22,17 +20,11 @@ OptolinkNumber = optolink_ns.class_( 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_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_BYTES): cv.one_of(1, 2, 4, int=True), } ) .extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/optolink/optolink_text_sensor.cpp b/esphome/components/optolink/optolink_text_sensor.cpp index 700e9578f7..f3e096684f 100644 --- a/esphome/components/optolink/optolink_text_sensor.cpp +++ b/esphome/components/optolink/optolink_text_sensor.cpp @@ -8,18 +8,37 @@ namespace esphome { namespace optolink { void OptolinkTextSensor::setup() { - if (!raw_) { - setup_datapoint_(); - } else { + if (mode_ == RAW) { datapoint_ = new Datapoint<convRaw>(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: <raw>", dp.getGroup(), dp.getName()); uint8_t buffer[bytes_ + 1]; dp_value.getRaw(buffer); buffer[bytes_] = 0x0; + ESP_LOGD("OptolinkTextSensor", "Datapoint %s - %s: %s", dp.getGroup(), dp.getName(), buffer); publish_state((char *) buffer); }); + } else if (mode_ == DAY_SCHEDULE) { + datapoint_ = new Datapoint<convRaw>(get_sensor_name().c_str(), "optolink", address_ + 8 * dow_, writeable_); + datapoint_->setLength(8); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { + uint8_t data[8]; + dp_value.getRaw(data); + ESP_LOGD("OptolinkTextSensor", "Datapoint %s - %s", dp.getGroup(), dp.getName()); + char buffer[100]; + for (int i = 0; i < 8; i++) { + if (data[i] != 0xFF) { + int hour = data[i] >> 3; + int minute = (data[i] & 0b111) * 10; + sprintf(buffer + i * 6, "%02d:%02d ", hour, minute); + } else { + sprintf(buffer + i * 6, " "); + } + } + publish_state(buffer); + }); + } else { + setup_datapoint_(); } }; diff --git a/esphome/components/optolink/optolink_text_sensor.h b/esphome/components/optolink/optolink_text_sensor.h index 950b68f1cb..fd6a64a60e 100644 --- a/esphome/components/optolink/optolink_text_sensor.h +++ b/esphome/components/optolink/optolink_text_sensor.h @@ -10,23 +10,27 @@ namespace esphome { namespace optolink { +enum TextSensorMode { MAP, RAW, DAY_SCHEDULE }; + 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; } + void set_mode(TextSensorMode mode) { mode_ = mode; } + void set_day_of_week(int dow) { dow_ = dow; } 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)); }; + void value_changed(float state) override { publish_state(std::to_string((uint32_t) state)); }; private: - bool raw_ = false; + TextSensorMode mode_ = MAP; + int dow_ = 0; }; } // namespace optolink diff --git a/esphome/components/optolink/select.py b/esphome/components/optolink/select.py index 5d6aa2ff85..779800efef 100644 --- a/esphome/components/optolink/select.py +++ b/esphome/components/optolink/select.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,9 +8,8 @@ from esphome.const import ( CONF_FROM, CONF_ID, CONF_TO, - CONF_UPDATE_INTERVAL, ) -from . import OptolinkComponent, optolink_ns, CONF_OPTOLINK_ID +from . import optolink_ns, CONF_OPTOLINK_ID from .sensor import SENSOR_BASE_SCHEMA OptolinkSelect = optolink_ns.class_( @@ -37,18 +35,12 @@ 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_BYTES): cv.one_of(1, 2, 4, int=True), } ) .extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/optolink/sensor.py b/esphome/components/optolink/sensor.py index 4991ac110d..a403f302a8 100644 --- a/esphome/components/optolink/sensor.py +++ b/esphome/components/optolink/sensor.py @@ -1,38 +1,22 @@ -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, + CONF_ID, ) -from . import optolink_ns, OptolinkComponent +from . import CONF_OPTOLINK_ID, SENSOR_BASE_SCHEMA, optolink_ns 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) - ), - ), + cv.Required(CONF_BYTES): cv.one_of(1, 2, 4, int=True), } ) .extend(SENSOR_BASE_SCHEMA) diff --git a/esphome/components/optolink/text_sensor.py b/esphome/components/optolink/text_sensor.py index 09280c91aa..ea6fb4c0bf 100644 --- a/esphome/components/optolink/text_sensor.py +++ b/esphome/components/optolink/text_sensor.py @@ -1,4 +1,3 @@ -from esphome import core import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor @@ -7,32 +6,71 @@ from esphome.const import ( CONF_BYTES, CONF_DIV_RATIO, CONF_ID, - CONF_RAW, - CONF_UPDATE_INTERVAL, + CONF_MODE, ) -from . import optolink_ns, OptolinkComponent, CONF_OPTOLINK_ID +from . import optolink_ns, CONF_OPTOLINK_ID from .sensor import SENSOR_BASE_SCHEMA OptolinkTextSensor = optolink_ns.class_( "OptolinkTextSensor", text_sensor.TextSensor, cg.PollingComponent ) +TextSensorMode = optolink_ns.enum("TextSensorMode") +MODE = { + "MAP": TextSensorMode.MAP, + "RAW": TextSensorMode.RAW, + "DAY_SCHEDULE": TextSensorMode.DAY_SCHEDULE, +} + +DAY_OF_WEEK = { + "MONDAY": 0, + "TUESDAY": 1, + "WEDNESDAY": 2, + "THURSDAY": 3, + "FRIDAY": 4, + "SATURDAY": 5, + "SUNDAY": 6, +} + +CONF_DOW = "day_of_week" + + +def check_bytes(): + def validator_(config): + bytes_needed = config[CONF_MODE] in ["MAP", "RAW"] + bytes_defined = CONF_BYTES in config + if bytes_needed and not bytes_defined: + raise cv.Invalid(f"{CONF_BYTES} is required in mode MAP or RAW") + if not bytes_needed and bytes_defined: + raise cv.Invalid(f"{CONF_BYTES} is not allowed in mode DAY_SCHEDULE") + return config + + return validator_ + + +def check_dow(): + def validator_(config): + if config[CONF_MODE] == "DAY_SCHEDULE" and CONF_DOW not in config: + raise cv.Invalid(f"{CONF_DOW} is required in mode DAY_SCHEDULE") + if config[CONF_MODE] != "DAY_SCHEDULE" and CONF_DOW in config: + raise cv.Invalid(f"{CONF_DOW} is only allowed in mode DAY_SCHEDULE") + return config + + return validator_ + + 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, + cv.Optional(CONF_MODE, default="MAP"): cv.enum(MODE, upper=True), + cv.Optional(CONF_BYTES): cv.int_range(min=1, max=9), + cv.Optional(CONF_DOW): cv.enum(DAY_OF_WEEK, upper=True), } ) - .extend(SENSOR_BASE_SCHEMA) - .extend({cv.Required(CONF_BYTES): cv.int_}), + .extend(SENSOR_BASE_SCHEMA), + check_bytes(), + check_dow(), ) @@ -43,7 +81,10 @@ async def to_code(config): 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_mode(config[CONF_MODE])) 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_BYTES in config: + cg.add(var.set_bytes(config[CONF_BYTES])) + if CONF_DOW in config: + cg.add(var.set_day_of_week(config[CONF_DOW]))