From 29113808ee7fe079285139ca09c2a1d71b3de037 Mon Sep 17 00:00:00 2001 From: jakehdk <94864542+jakehdk@users.noreply.github.com> Date: Sun, 12 Mar 2023 21:14:00 +0100 Subject: [PATCH 01/10] Add support for new clones of mpu6050 responding with 0x70 address (#4546) Co-authored-by: jakehdk --- esphome/components/mpu6050/mpu6050.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/mpu6050/mpu6050.cpp b/esphome/components/mpu6050/mpu6050.cpp index cc426e58a2..51e3ec2383 100644 --- a/esphome/components/mpu6050/mpu6050.cpp +++ b/esphome/components/mpu6050/mpu6050.cpp @@ -23,7 +23,8 @@ const float GRAVITY_EARTH = 9.80665f; void MPU6050Component::setup() { ESP_LOGCONFIG(TAG, "Setting up MPU6050..."); uint8_t who_am_i; - if (!this->read_byte(MPU6050_REGISTER_WHO_AM_I, &who_am_i) || (who_am_i != 0x68 && who_am_i != 0x98)) { + if (!this->read_byte(MPU6050_REGISTER_WHO_AM_I, &who_am_i) || + (who_am_i != 0x68 && who_am_i != 0x70 && who_am_i != 0x98)) { this->mark_failed(); return; } From 5a56644702dc3fe277144ad12e115b5a75f4d3e2 Mon Sep 17 00:00:00 2001 From: Martin Murray Date: Sun, 12 Mar 2023 16:16:48 -0400 Subject: [PATCH 02/10] Add carbon dioxide device class to scd30 sensor schema. (#4547) --- esphome/components/scd30/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/scd30/sensor.py b/esphome/components/scd30/sensor.py index ffbf90338f..1ddf0f1e85 100644 --- a/esphome/components/scd30/sensor.py +++ b/esphome/components/scd30/sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_CO2, CONF_UPDATE_INTERVAL, CONF_VALUE, + DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -46,6 +47,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( From 32a0a604807fb81382b254d755fdff149f286bf6 Mon Sep 17 00:00:00 2001 From: Dorian Zedler Date: Sun, 12 Mar 2023 21:26:27 +0100 Subject: [PATCH 03/10] Feat: add support for hex color in color component (#4493) * Feat: add support for hex color in color component * Chore: move hex color validator to color component * Chore: add test * Chore: fix formatting * Chore: make linter happy * Chore: make linter happy * Fix: parse correct offsets Co-authored-by: Oxan van Leeuwen * Chore: use cv.Invalid * Fix: remove # because it indicates a comment in yaml * Fix: only allow hex if no other color value is set * Fix: tests * Fix: mutual exclusion of raw and hex colors * Chore: format file * Update __init__.py --------- Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/color/__init__.py | 59 +++++++++++++++++++++------- tests/test1.yaml | 2 + 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/esphome/components/color/__init__.py b/esphome/components/color/__init__.py index 47679fcc68..9a85eace75 100644 --- a/esphome/components/color/__init__.py +++ b/esphome/components/color/__init__.py @@ -10,23 +10,42 @@ CONF_RED_INT = "red_int" CONF_GREEN_INT = "green_int" CONF_BLUE_INT = "blue_int" CONF_WHITE_INT = "white_int" - -CONFIG_SCHEMA = cv.Schema( - { - cv.Required(CONF_ID): cv.declare_id(ColorStruct), - cv.Exclusive(CONF_RED, "red"): cv.percentage, - cv.Exclusive(CONF_RED_INT, "red"): cv.uint8_t, - cv.Exclusive(CONF_GREEN, "green"): cv.percentage, - cv.Exclusive(CONF_GREEN_INT, "green"): cv.uint8_t, - cv.Exclusive(CONF_BLUE, "blue"): cv.percentage, - cv.Exclusive(CONF_BLUE_INT, "blue"): cv.uint8_t, - cv.Exclusive(CONF_WHITE, "white"): cv.percentage, - cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t, - } -).extend(cv.COMPONENT_SCHEMA) +CONF_HEX = "hex" -async def to_code(config): +def hex_color(value): + if len(value) != 6: + raise cv.Invalid("Color must have six digits") + try: + return (int(value[0:2], 16), int(value[2:4], 16), int(value[4:6], 16)) + except ValueError as exc: + raise cv.Invalid("Color must be hexadecimal") from exc + + +CONFIG_SCHEMA = cv.Any( + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(ColorStruct), + cv.Exclusive(CONF_RED, "red"): cv.percentage, + cv.Exclusive(CONF_RED_INT, "red"): cv.uint8_t, + cv.Exclusive(CONF_GREEN, "green"): cv.percentage, + cv.Exclusive(CONF_GREEN_INT, "green"): cv.uint8_t, + cv.Exclusive(CONF_BLUE, "blue"): cv.percentage, + cv.Exclusive(CONF_BLUE_INT, "blue"): cv.uint8_t, + cv.Exclusive(CONF_WHITE, "white"): cv.percentage, + cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t, + } + ).extend(cv.COMPONENT_SCHEMA), + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(ColorStruct), + cv.Required(CONF_HEX): hex_color, + } + ).extend(cv.COMPONENT_SCHEMA), +) + + +def from_rgbw(config): r = 0 if CONF_RED in config: r = int(config[CONF_RED] * 255) @@ -51,6 +70,16 @@ async def to_code(config): elif CONF_WHITE_INT in config: w = config[CONF_WHITE_INT] + return (r, g, b, w) + + +async def to_code(config): + if CONF_HEX in config: + r, g, b = config[CONF_HEX] + w = 0 + else: + r, g, b, w = from_rgbw(config) + cg.new_variable( config[CONF_ID], cg.StructInitializer(ColorStruct, ("r", r), ("g", g), ("b", b), ("w", w)), diff --git a/tests/test1.yaml b/tests/test1.yaml index 2af71ea745..ef389ea712 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2528,6 +2528,8 @@ color: red: 0% green: 1% blue: 100% + - id: kbx_green + hex: "3DEC55" display: - platform: lcd_gpio From ea17a92dbc6bc663482374fe22806c5b246c5f98 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 13 Mar 2023 11:43:31 +1300 Subject: [PATCH 04/10] Allow AUTO_LOAD to be a function (#4550) --- esphome/loader.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/loader.py b/esphome/loader.py index b245fa1610..cd21e5a509 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -71,7 +71,10 @@ class ComponentManifest: @property def auto_load(self) -> list[str]: - return getattr(self.module, "AUTO_LOAD", []) + al = getattr(self.module, "AUTO_LOAD", []) + if callable(al): + return al() + return al @property def codeowners(self) -> list[str]: From 6a6aee510d3bedbc2407dfd7870be4865a613bdc Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 13 Mar 2023 01:13:19 +0100 Subject: [PATCH 05/10] On the ILI9xxx display's enable the psram on esp32 and allow big screen (#4551) * enable the psram on esp32 and allow big screen * update CODEOWNERS * small update * update CODEOWNERS again. * Removed the M5STACK because it is a ESP32 device. * i removed the wrong model * update the error message. --- esphome/components/ili9xxx/display.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 437fc93b89..780c64ec70 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -16,7 +16,13 @@ from esphome.const import ( ) DEPENDENCIES = ["spi"] -AUTO_LOAD = ["psram"] + + +def AUTO_LOAD(): + if CORE.is_esp32: + return ["psram"] + return [] + CODEOWNERS = ["@nielsnl68"] @@ -60,6 +66,16 @@ def _validate(config): raise cv.Invalid( "Providing color palette images requires palette mode to be 'IMAGE_ADAPTIVE'" ) + if CORE.is_esp8266 and config.get(CONF_MODEL) not in [ + "M5STACK", + "TFT_2.4", + "TFT_2.4R", + "ILI9341", + "ILI9342", + ]: + raise cv.Invalid( + "Provided model can't run on ESP8266. Use an ESP32 with PSRAM onboard" + ) return config From d642aeba0ff5049661d3f36134a35966fa121582 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 13 Mar 2023 13:13:36 +1300 Subject: [PATCH 06/10] Map gpio pins for touch on esp32-s2/s3 (#4552) * Map gpio pins for touch on esp32-s2/s3 * fix value --- .../components/esp32_touch/binary_sensor.py | 73 +++++++++++++++---- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index 326f559830..2cdf1343c3 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.core import CORE from esphome.components import binary_sensor from esphome.const import ( CONF_PIN, @@ -7,6 +8,13 @@ from esphome.const import ( CONF_ID, ) from esphome.components.esp32 import gpio +from esphome.components.esp32.const import ( + KEY_ESP32, + KEY_VARIANT, + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) from . import esp32_touch_ns, ESP32TouchComponent DEPENDENCIES = ["esp32_touch", "esp32"] @@ -15,24 +23,63 @@ CONF_ESP32_TOUCH_ID = "esp32_touch_id" CONF_WAKEUP_THRESHOLD = "wakeup_threshold" TOUCH_PADS = { - 4: cg.global_ns.TOUCH_PAD_NUM0, - 0: cg.global_ns.TOUCH_PAD_NUM1, - 2: cg.global_ns.TOUCH_PAD_NUM2, - 15: cg.global_ns.TOUCH_PAD_NUM3, - 13: cg.global_ns.TOUCH_PAD_NUM4, - 12: cg.global_ns.TOUCH_PAD_NUM5, - 14: cg.global_ns.TOUCH_PAD_NUM6, - 27: cg.global_ns.TOUCH_PAD_NUM7, - 33: cg.global_ns.TOUCH_PAD_NUM8, - 32: cg.global_ns.TOUCH_PAD_NUM9, + VARIANT_ESP32: { + 4: cg.global_ns.TOUCH_PAD_NUM0, + 0: cg.global_ns.TOUCH_PAD_NUM1, + 2: cg.global_ns.TOUCH_PAD_NUM2, + 15: cg.global_ns.TOUCH_PAD_NUM3, + 13: cg.global_ns.TOUCH_PAD_NUM4, + 12: cg.global_ns.TOUCH_PAD_NUM5, + 14: cg.global_ns.TOUCH_PAD_NUM6, + 27: cg.global_ns.TOUCH_PAD_NUM7, + 33: cg.global_ns.TOUCH_PAD_NUM8, + 32: cg.global_ns.TOUCH_PAD_NUM9, + }, + VARIANT_ESP32S2: { + 1: cg.global_ns.TOUCH_PAD_NUM1, + 2: cg.global_ns.TOUCH_PAD_NUM2, + 3: cg.global_ns.TOUCH_PAD_NUM3, + 4: cg.global_ns.TOUCH_PAD_NUM4, + 5: cg.global_ns.TOUCH_PAD_NUM5, + 6: cg.global_ns.TOUCH_PAD_NUM6, + 7: cg.global_ns.TOUCH_PAD_NUM7, + 8: cg.global_ns.TOUCH_PAD_NUM8, + 9: cg.global_ns.TOUCH_PAD_NUM9, + 10: cg.global_ns.TOUCH_PAD_NUM10, + 11: cg.global_ns.TOUCH_PAD_NUM11, + 12: cg.global_ns.TOUCH_PAD_NUM12, + 13: cg.global_ns.TOUCH_PAD_NUM13, + 14: cg.global_ns.TOUCH_PAD_NUM14, + }, + VARIANT_ESP32S3: { + 1: cg.global_ns.TOUCH_PAD_NUM1, + 2: cg.global_ns.TOUCH_PAD_NUM2, + 3: cg.global_ns.TOUCH_PAD_NUM3, + 4: cg.global_ns.TOUCH_PAD_NUM4, + 5: cg.global_ns.TOUCH_PAD_NUM5, + 6: cg.global_ns.TOUCH_PAD_NUM6, + 7: cg.global_ns.TOUCH_PAD_NUM7, + 8: cg.global_ns.TOUCH_PAD_NUM8, + 9: cg.global_ns.TOUCH_PAD_NUM9, + 10: cg.global_ns.TOUCH_PAD_NUM10, + 11: cg.global_ns.TOUCH_PAD_NUM11, + 12: cg.global_ns.TOUCH_PAD_NUM12, + 13: cg.global_ns.TOUCH_PAD_NUM13, + 14: cg.global_ns.TOUCH_PAD_NUM14, + }, } def validate_touch_pad(value): value = gpio.validate_gpio_pin(value) - if value not in TOUCH_PADS: + variant = CORE.data[KEY_ESP32][KEY_VARIANT] + if variant not in TOUCH_PADS: + raise cv.Invalid(f"ESP32 variant {variant} does not support touch pads.") + + pads = TOUCH_PADS[variant] + if value not in pads: raise cv.Invalid(f"Pin {value} does not support touch pads.") - return value + return cv.enum(pads)(value) ESP32TouchBinarySensor = esp32_touch_ns.class_( @@ -53,7 +100,7 @@ async def to_code(config): hub = await cg.get_variable(config[CONF_ESP32_TOUCH_ID]) var = cg.new_Pvariable( config[CONF_ID], - TOUCH_PADS[config[CONF_PIN]], + config[CONF_PIN], config[CONF_THRESHOLD], config[CONF_WAKEUP_THRESHOLD], ) From 112439b4850c491f385d90b487d63c1558ad369b Mon Sep 17 00:00:00 2001 From: jota29 Date: Sat, 11 Mar 2023 07:03:50 +0000 Subject: [PATCH 07/10] Add support for Viessmann heating components via Optolink adapter --- esphome/components/optolink/__init__.py | 92 ++++++++++ esphome/components/optolink/binary_sensor.py | 31 ++++ esphome/components/optolink/number.py | 59 +++++++ esphome/components/optolink/optolink.cpp | 72 ++++++++ esphome/components/optolink/optolink.h | 49 ++++++ .../optolink/optolink_binary_sensor.h | 28 +++ .../optolink/optolink_device_info_sensor.h | 43 +++++ .../components/optolink/optolink_number.cpp | 20 +++ esphome/components/optolink/optolink_number.h | 26 +++ .../components/optolink/optolink_select.cpp | 41 +++++ esphome/components/optolink/optolink_select.h | 39 +++++ esphome/components/optolink/optolink_sensor.h | 25 +++ .../optolink/optolink_sensor_base.cpp | 163 ++++++++++++++++++ .../optolink/optolink_sensor_base.h | 47 +++++ .../optolink/optolink_state_sensor.h | 28 +++ .../components/optolink/optolink_switch.cpp | 20 +++ esphome/components/optolink/optolink_switch.h | 29 ++++ .../optolink/optolink_text_sensor.cpp | 25 +++ .../optolink/optolink_text_sensor.h | 31 ++++ esphome/components/optolink/select.py | 80 +++++++++ esphome/components/optolink/sensor.py | 51 ++++++ esphome/components/optolink/switch.py | 33 ++++ esphome/components/optolink/text_sensor.py | 50 ++++++ 23 files changed, 1082 insertions(+) create mode 100644 esphome/components/optolink/__init__.py create mode 100644 esphome/components/optolink/binary_sensor.py create mode 100644 esphome/components/optolink/number.py create mode 100644 esphome/components/optolink/optolink.cpp create mode 100755 esphome/components/optolink/optolink.h create mode 100644 esphome/components/optolink/optolink_binary_sensor.h create mode 100644 esphome/components/optolink/optolink_device_info_sensor.h create mode 100644 esphome/components/optolink/optolink_number.cpp create mode 100644 esphome/components/optolink/optolink_number.h create mode 100644 esphome/components/optolink/optolink_select.cpp create mode 100644 esphome/components/optolink/optolink_select.h create mode 100644 esphome/components/optolink/optolink_sensor.h create mode 100644 esphome/components/optolink/optolink_sensor_base.cpp create mode 100644 esphome/components/optolink/optolink_sensor_base.h create mode 100644 esphome/components/optolink/optolink_state_sensor.h create mode 100644 esphome/components/optolink/optolink_switch.cpp create mode 100644 esphome/components/optolink/optolink_switch.h create mode 100644 esphome/components/optolink/optolink_text_sensor.cpp create mode 100644 esphome/components/optolink/optolink_text_sensor.h create mode 100644 esphome/components/optolink/select.py create mode 100644 esphome/components/optolink/sensor.py create mode 100644 esphome/components/optolink/switch.py create mode 100644 esphome/components/optolink/text_sensor.py diff --git a/esphome/components/optolink/__init__.py b/esphome/components/optolink/__init__.py new file mode 100644 index 0000000000..bd409e4a09 --- /dev/null +++ b/esphome/components/optolink/__init__.py @@ -0,0 +1,92 @@ +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_ID, + CONF_LOGGER, + CONF_PROTOCOL, + CONF_RX_PIN, + CONF_STATE, + CONF_TX_PIN, +) +from esphome.core import CORE + +DEPENDENCIES = [] +AUTO_LOAD = ["sensor", "binary_sensor", "text_sensor", "number", "select", "switch"] +MULTI_CONF = False +CONF_DEVICE_INFO = "device_info" + +optolink_ns = cg.esphome_ns.namespace("optolink") +OptolinkComponent = optolink_ns.class_("Optolink", cg.Component) +StateSensor = optolink_ns.class_( + "OptolinkStateSensor", ts.TextSensor, cg.PollingComponent +) +STATE_SENSOR_ID = "state_sensor_id" +DeviceInfoSensor = optolink_ns.class_( + "OptolinkDeviceInfoSensor", ts.TextSensor, cg.PollingComponent +) +DEVICE_INFO_SENSOR_ID = "device_info_sensor_id" +CONFIG_SCHEMA = 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_LOGGER, default=False): cv.boolean, + cv.Optional(CONF_STATE): cv.string, + cv.Optional(CONF_DEVICE_INFO): cv.string, + } +).extend(cv.COMPONENT_SCHEMA) +if CORE.is_esp32: + CONFIG_SCHEMA = CONFIG_SCHEMA.extend( + cv.Schema( + { + cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_schema, + cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_schema, + } + ) + ) + + +async def to_code(config): + cg.add_library("VitoWiFi", "1.0.2") + + cg.add_define( + "VITOWIFI_PROTOCOL", cg.RawExpression(f"Optolink{config[CONF_PROTOCOL]}") + ) + + 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"])) + + await cg.register_component(var, config) diff --git a/esphome/components/optolink/binary_sensor.py b/esphome/components/optolink/binary_sensor.py new file mode 100644 index 0000000000..57f51f2c3e --- /dev/null +++ b/esphome/components/optolink/binary_sensor.py @@ -0,0 +1,31 @@ +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 optolink_ns, OptolinkComponent + +OptolinkBinarySensor = optolink_ns.class_( + "OptolinkBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent +) +CONF_OPTOLINK_ID = "optolink_id" +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/number.py b/esphome/components/optolink/number.py new file mode 100644 index 0000000000..2de32e186a --- /dev/null +++ b/esphome/components/optolink/number.py @@ -0,0 +1,59 @@ +from esphome import core +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import number +from esphome.components.optolink.sensor import SENSOR_BASE_SCHEMA +from esphome.const import ( + CONF_ADDRESS, + CONF_BYTES, + CONF_DIV_RATIO, + CONF_ID, + CONF_MAX_VALUE, + CONF_MIN_VALUE, + CONF_STEP, + CONF_UPDATE_INTERVAL, +) +from . import OptolinkComponent, optolink_ns + +OptolinkNumber = optolink_ns.class_( + "OptolinkNumber", number.Number, cg.PollingComponent +) + +CONF_OPTOLINK_ID = "optolink_id" +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) + ), + ), + } + ) + .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 number.register_number( + var, + config, + min_value=config[CONF_MIN_VALUE], + max_value=config[CONF_MAX_VALUE], + step=config[CONF_STEP], + ) + + 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/optolink.cpp b/esphome/components/optolink/optolink.cpp new file mode 100644 index 0000000000..3fbad9732d --- /dev/null +++ b/esphome/components/optolink/optolink.cpp @@ -0,0 +1,72 @@ +#include "esphome/core/defines.h" +#include "esphome/components/optolink/optolink.h" +#include + +VitoWiFiClass VitoWiFi; + +namespace esphome { +namespace optolink { + +void Optolink::_comm() { + ESP_LOGD("Optolink", "enter _comm"); + VitoWiFi.readAll(); + ESP_LOGD("Optolink", "exit _comm"); +} + +void Optolink::setup() { + ESP_LOGI("Optolink", "setup"); + + if (logger_enabled_) { + VitoWiFi.setLogger(this); + VitoWiFi.enableLogger(); + } + +#if defined(USE_ESP32) + VitoWiFi.setup(&Serial, rx_pin_, tx_pin_); +#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::set_error(const std::string &format, ...) { + va_list args; + va_start(args, format); + char buffer[128]; + size_t n = std::vsnprintf(buffer, sizeof(buffer), format.c_str(), args); + va_end(args); + + error_ = buffer; +} + +void Optolink::read_value(IDatapoint *datapoint) { + if (datapoint != nullptr) { + ESP_LOGI("Optolink", " read value of datapoint %s", datapoint->getName()); + VitoWiFi.readDatapoint(*datapoint); + } +} + +void Optolink::write_value(IDatapoint *datapoint, DPValue dpValue) { + if (datapoint != nullptr) { + char buffer[64]; + dpValue.getString(buffer, sizeof(buffer)); + ESP_LOGI("Optolink", " write value %s of datapoint %s", buffer, datapoint->getName()); + VitoWiFi.writeDatapoint(*datapoint, dpValue); + } +} + +size_t Optolink::write(uint8_t ch) { + if (ch == '\n') { + ESP_LOGD("VitoWifi", "%s", log_buffer_.c_str()); + log_buffer_.clear(); + } else { + log_buffer_.push_back(ch); + } + return 1; +} + +} // namespace optolink +} // namespace esphome diff --git a/esphome/components/optolink/optolink.h b/esphome/components/optolink/optolink.h new file mode 100755 index 0000000000..3e64c62de4 --- /dev/null +++ b/esphome/components/optolink/optolink.h @@ -0,0 +1,49 @@ +#pragma once + +#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 + +using namespace esphome; +using namespace sensor; +using namespace binary_sensor; +using namespace text_sensor; + +namespace esphome { +namespace optolink { + +// '00' ='WW' '01' ='RED' '02' ='NORM' '03' ='H+WW' '04' ='H+WW FS' '05' ='ABSCHALT' + +//===================================================================================================================== +class Optolink : public esphome::Component, public Print { + protected: + std::string error_ = "OK"; + std::string log_buffer_; + bool logger_enabled_ = false; + int rx_pin_; + int tx_pin_; + + void _comm(); + + public: + void setup() override; + + void loop() override; + + size_t write(uint8_t ch) override; + + void set_logger_enabled(bool logger_enabled) { logger_enabled_ = logger_enabled; } + 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 dpValue); + void read_value(IDatapoint *datapoint); + + void set_error(const std::string &format, ...); + std::string get_error() { return error_; } +}; + +} // namespace optolink +} // namespace esphome diff --git a/esphome/components/optolink/optolink_binary_sensor.h b/esphome/components/optolink/optolink_binary_sensor.h new file mode 100644 index 0000000000..22d8dd5878 --- /dev/null +++ b/esphome/components/optolink/optolink_binary_sensor.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "optolink.h" +#include "optolink_sensor_base.h" +#include + +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 std::string &get_sensor_name() override { return get_name(); } + void value_changed(float state) override { publish_state(state); }; +}; +} // namespace optolink +} // namespace esphome diff --git a/esphome/components/optolink/optolink_device_info_sensor.h b/esphome/components/optolink/optolink_device_info_sensor.h new file mode 100644 index 0000000000..7611459807 --- /dev/null +++ b/esphome/components/optolink/optolink_device_info_sensor.h @@ -0,0 +1,43 @@ +#pragma once + +#include "esphome/components/text_sensor/text_sensor.h" +#include "optolink.h" +#include "optolink_sensor_base.h" +#include + +namespace esphome { +namespace optolink { + +class OptolinkDeviceInfoSensor : public esphome::text_sensor::TextSensor, public esphome::PollingComponent { + public: + OptolinkDeviceInfoSensor(std::string name, Optolink *optolink) { + optolink_ = optolink; + set_name(name); + set_update_interval(1800000); + set_entity_category(esphome::ENTITY_CATEGORY_DIAGNOSTIC); + } + + protected: + void setup() override { + datapoint_ = new Datapoint(get_name().c_str(), "optolink", 0x00f8, false); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dpValue) { + uint32_t value = dpValue.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); + }); + } + void update() override { optolink_->read_value(datapoint_); } + + private: + Optolink *optolink_; + IDatapoint *datapoint_; +}; + +} // namespace optolink +} // namespace esphome diff --git a/esphome/components/optolink/optolink_number.cpp b/esphome/components/optolink/optolink_number.cpp new file mode 100644 index 0000000000..1032645009 --- /dev/null +++ b/esphome/components/optolink/optolink_number.cpp @@ -0,0 +1,20 @@ +#include "optolink_number.h" +#include "optolink.h" +#include + +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 diff --git a/esphome/components/optolink/optolink_number.h b/esphome/components/optolink/optolink_number.h new file mode 100644 index 0000000000..2cc73a46db --- /dev/null +++ b/esphome/components/optolink/optolink_number.h @@ -0,0 +1,26 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "optolink_sensor_base.h" +#include "optolink.h" +#include + +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 std::string &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 diff --git a/esphome/components/optolink/optolink_select.cpp b/esphome/components/optolink/optolink_select.cpp new file mode 100644 index 0000000000..f7c53077f6 --- /dev/null +++ b/esphome/components/optolink/optolink_select.cpp @@ -0,0 +1,41 @@ +#include "optolink_select.h" +#include "optolink.h" +#include + +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 diff --git a/esphome/components/optolink/optolink_select.h b/esphome/components/optolink/optolink_select.h new file mode 100644 index 0000000000..63b379c1e5 --- /dev/null +++ b/esphome/components/optolink/optolink_select.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include "esphome/components/select/select.h" +#include "optolink.h" +#include "optolink_sensor_base.h" +#include + +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->begin(); it != mapping->end(); ++it) { + values.push_back(it->second); + } + traits.set_options(values); + }; + + protected: + void setup() override { setup_datapoint(); } + void update() override { optolink_->read_value(datapoint_); } + + const std::string &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 diff --git a/esphome/components/optolink/optolink_sensor.h b/esphome/components/optolink/optolink_sensor.h new file mode 100644 index 0000000000..b8613fc470 --- /dev/null +++ b/esphome/components/optolink/optolink_sensor.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "optolink.h" +#include "optolink_sensor_base.h" +#include + +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 std::string &get_sensor_name() override { return get_name(); } + void value_changed(float state) override { publish_state(state); }; +}; +} // namespace optolink +} // namespace esphome diff --git a/esphome/components/optolink/optolink_sensor_base.cpp b/esphome/components/optolink/optolink_sensor_base.cpp new file mode 100644 index 0000000000..599f4b791d --- /dev/null +++ b/esphome/components/optolink/optolink_sensor_base.cpp @@ -0,0 +1,163 @@ +#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: + optolink_->write_value(datapoint_, DPValue((float) value)); + break; + 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 dpValue) { + ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %d", dp.getGroup(), dp.getName(), dpValue.getU8()); + value_changed(dpValue.getU8()); + }); + break; + case 10: + datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dpValue) { + ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dpValue.getFloat()); + value_changed(dpValue.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 dpValue) { + ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %d", dp.getGroup(), dp.getName(), dpValue.getU16()); + value_changed(dpValue.getU16()); + }); + break; + case 10: + datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dpValue) { + ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dpValue.getFloat()); + value_changed(dpValue.getFloat()); + }); + break; + case 100: + datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dpValue) { + ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dpValue.getFloat()); + value_changed(dpValue.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 dpValue) { + ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %d", dp.getGroup(), dp.getName(), dpValue.getU32()); + value_changed(dpValue.getU32()); + }); + break; + case 3600: + datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dpValue) { + ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dpValue.getFloat()); + value_changed(dpValue.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 diff --git a/esphome/components/optolink/optolink_sensor_base.h b/esphome/components/optolink/optolink_sensor_base.h new file mode 100644 index 0000000000..93074ea2f2 --- /dev/null +++ b/esphome/components/optolink/optolink_sensor_base.h @@ -0,0 +1,47 @@ + +#pragma once + +#include "esphome/core/log.h" +#include + +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 std::string &get_sensor_name() = 0; + virtual void value_changed(float state) = 0; +}; + +class conv2_100_F : public DPType { + public: + void encode(uint8_t *out, DPValue in); + DPValue decode(const uint8_t *in); + const size_t getLength() const { return 2; } +}; + +} // namespace optolink +} // namespace esphome diff --git a/esphome/components/optolink/optolink_state_sensor.h b/esphome/components/optolink/optolink_state_sensor.h new file mode 100644 index 0000000000..78d99ac19d --- /dev/null +++ b/esphome/components/optolink/optolink_state_sensor.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/text_sensor/text_sensor.h" +#include "optolink.h" +#include "optolink_sensor_base.h" +#include + +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); + 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 diff --git a/esphome/components/optolink/optolink_switch.cpp b/esphome/components/optolink/optolink_switch.cpp new file mode 100644 index 0000000000..226dd3d484 --- /dev/null +++ b/esphome/components/optolink/optolink_switch.cpp @@ -0,0 +1,20 @@ +#include "optolink_switch.h" +#include "optolink.h" +#include + +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 diff --git a/esphome/components/optolink/optolink_switch.h b/esphome/components/optolink/optolink_switch.h new file mode 100644 index 0000000000..faf283dacf --- /dev/null +++ b/esphome/components/optolink/optolink_switch.h @@ -0,0 +1,29 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "optolink_sensor_base.h" +#include "optolink.h" +#include + +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 std::string &get_sensor_name() override { return get_name(); } + void value_changed(float state) override { publish_state(state); }; + + void write_state(bool state) override; +}; + +} // namespace optolink +} // namespace esphome diff --git a/esphome/components/optolink/optolink_text_sensor.cpp b/esphome/components/optolink/optolink_text_sensor.cpp new file mode 100644 index 0000000000..f226f4f5d0 --- /dev/null +++ b/esphome/components/optolink/optolink_text_sensor.cpp @@ -0,0 +1,25 @@ +#include "optolink_text_sensor.h" +#include "optolink.h" +#include + +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 dpValue) { + ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: ", dp.getGroup(), dp.getName()); + uint8_t buffer[bytes_ + 1]; + dpValue.getRaw(buffer); + buffer[bytes_] = 0x0; + publish_state((char *) buffer); + }); + } +}; + +} // namespace optolink +} // namespace esphome diff --git a/esphome/components/optolink/optolink_text_sensor.h b/esphome/components/optolink/optolink_text_sensor.h new file mode 100644 index 0000000000..bb181c3965 --- /dev/null +++ b/esphome/components/optolink/optolink_text_sensor.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/components/text_sensor/text_sensor.h" +#include "optolink.h" +#include "optolink_sensor_base.h" +#include + +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 std::string &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 diff --git a/esphome/components/optolink/select.py b/esphome/components/optolink/select.py new file mode 100644 index 0000000000..34c3482206 --- /dev/null +++ b/esphome/components/optolink/select.py @@ -0,0 +1,80 @@ +from esphome import core +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import select +from esphome.components.optolink.sensor import SENSOR_BASE_SCHEMA +from esphome.const import ( + CONF_ADDRESS, + CONF_BYTES, + CONF_DIV_RATIO, + CONF_FROM, + CONF_ID, + CONF_TO, + CONF_UPDATE_INTERVAL, +) +from . import OptolinkComponent, optolink_ns + +OptolinkSelect = optolink_ns.class_( + "OptolinkSelect", select.Select, cg.PollingComponent +) + + +def validate_mapping(value): + if not isinstance(value, dict): + value = cv.string(value) + if "->" not in value: + raise cv.Invalid("Mapping must contain '->'") + a, b = value.split("->", 1) + value = {CONF_FROM: a.strip(), CONF_TO: b.strip()} + + return cv.Schema( + {cv.Required(CONF_FROM): cv.string, cv.Required(CONF_TO): cv.string} + )(value) + + +CONF_OPTOLINK_ID = "optolink_id" +CONF_MAP = "map" +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) + ), + ), + } + ) + .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 select.register_select( + var, + config, + options=[], + ) + + map_type_ = cg.std_ns.class_("map").template(cg.std_string, cg.std_string) + map_var = cg.new_Pvariable( + config[MAP_ID], + map_type_([(item[CONF_FROM], item[CONF_TO]) for item in config[CONF_MAP]]), + ) + + cg.add(var.set_map(map_var)) + 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.py b/esphome/components/optolink/sensor.py new file mode 100644 index 0000000000..4991ac110d --- /dev/null +++ b/esphome/components/optolink/sensor.py @@ -0,0 +1,51 @@ +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/switch.py b/esphome/components/optolink/switch.py new file mode 100644 index 0000000000..32540cdf0f --- /dev/null +++ b/esphome/components/optolink/switch.py @@ -0,0 +1,33 @@ +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/text_sensor.py b/esphome/components/optolink/text_sensor.py new file mode 100644 index 0000000000..662474236e --- /dev/null +++ b/esphome/components/optolink/text_sensor.py @@ -0,0 +1,50 @@ +from esphome import core +import esphome.codegen as cg +from esphome.components.optolink.sensor import SENSOR_BASE_SCHEMA +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 + +OptolinkTextSensor = optolink_ns.class_( + "OptolinkTextSensor", text_sensor.TextSensor, cg.PollingComponent +) + +CONF_OPTOLINK_ID = "optolink_id" +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])) From 7a89fbc1c3529f543da0ca1e5f068d608b4cccd5 Mon Sep 17 00:00:00 2001 From: jota29 Date: Sat, 11 Mar 2023 07:03:50 +0000 Subject: [PATCH 08/10] Added CODEOWNERS for new component --- CODEOWNERS | 1 + esphome/components/optolink/__init__.py | 1 + 2 files changed, 2 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index c006db2a6a..cee0adb332 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -180,6 +180,7 @@ esphome/components/nextion/switch/* @senexcrenshaw esphome/components/nextion/text_sensor/* @senexcrenshaw esphome/components/nfc/* @jesserockz esphome/components/number/* @esphome/core +esphome/components/optolink/* @j0ta29 esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core esphome/components/pca9554/* @hwstar diff --git a/esphome/components/optolink/__init__.py b/esphome/components/optolink/__init__.py index bd409e4a09..1944fd57a3 100644 --- a/esphome/components/optolink/__init__.py +++ b/esphome/components/optolink/__init__.py @@ -12,6 +12,7 @@ from esphome.const import ( ) from esphome.core import CORE +CODEOWNERS = ["@j0ta29"] DEPENDENCIES = [] AUTO_LOAD = ["sensor", "binary_sensor", "text_sensor", "number", "select", "switch"] MULTI_CONF = False From 06e4eaf139521b974d1b48bdabe49936eff6dc75 Mon Sep 17 00:00:00 2001 From: jota29 Date: Sat, 11 Mar 2023 17:48:14 +0000 Subject: [PATCH 09/10] fixed lint errors --- esphome/components/optolink/__init__.py | 2 + esphome/components/optolink/binary_sensor.py | 3 +- esphome/components/optolink/number.py | 5 +- esphome/components/optolink/optolink.cpp | 24 +++++---- esphome/components/optolink/optolink.h | 16 ++---- .../optolink/optolink_binary_sensor.h | 4 +- .../optolink/optolink_device_info_sensor.cpp | 22 +++++++++ .../optolink/optolink_device_info_sensor.h | 19 ++----- .../components/optolink/optolink_number.cpp | 4 +- esphome/components/optolink/optolink_number.h | 4 +- .../components/optolink/optolink_select.cpp | 4 +- esphome/components/optolink/optolink_select.h | 8 +-- esphome/components/optolink/optolink_sensor.h | 4 +- .../optolink/optolink_sensor_base.cpp | 49 +++++++++---------- .../optolink/optolink_sensor_base.h | 9 ++-- .../optolink/optolink_state_sensor.h | 2 +- .../components/optolink/optolink_switch.cpp | 4 +- esphome/components/optolink/optolink_switch.h | 6 +-- .../optolink/optolink_text_sensor.cpp | 8 +-- .../optolink/optolink_text_sensor.h | 2 +- esphome/components/optolink/select.py | 5 +- esphome/components/optolink/text_sensor.py | 5 +- platformio.ini | 1 + 23 files changed, 108 insertions(+), 102 deletions(-) mode change 100755 => 100644 esphome/components/optolink/optolink.h create mode 100644 esphome/components/optolink/optolink_device_info_sensor.cpp diff --git a/esphome/components/optolink/__init__.py b/esphome/components/optolink/__init__.py index 1944fd57a3..6359dbe64e 100644 --- a/esphome/components/optolink/__init__.py +++ b/esphome/components/optolink/__init__.py @@ -19,6 +19,8 @@ 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 diff --git a/esphome/components/optolink/binary_sensor.py b/esphome/components/optolink/binary_sensor.py index 57f51f2c3e..f55b9c6d16 100644 --- a/esphome/components/optolink/binary_sensor.py +++ b/esphome/components/optolink/binary_sensor.py @@ -3,12 +3,11 @@ 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 optolink_ns, OptolinkComponent +from . import OptolinkComponent, optolink_ns, CONF_OPTOLINK_ID OptolinkBinarySensor = optolink_ns.class_( "OptolinkBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent ) -CONF_OPTOLINK_ID = "optolink_id" CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(OptolinkBinarySensor).extend( { cv.GenerateID(CONF_OPTOLINK_ID): cv.use_id(OptolinkComponent), diff --git a/esphome/components/optolink/number.py b/esphome/components/optolink/number.py index 2de32e186a..2f4802cd9b 100644 --- a/esphome/components/optolink/number.py +++ b/esphome/components/optolink/number.py @@ -2,7 +2,6 @@ from esphome import core import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import number -from esphome.components.optolink.sensor import SENSOR_BASE_SCHEMA from esphome.const import ( CONF_ADDRESS, CONF_BYTES, @@ -13,13 +12,13 @@ from esphome.const import ( CONF_STEP, CONF_UPDATE_INTERVAL, ) -from . import OptolinkComponent, optolink_ns +from .sensor import SENSOR_BASE_SCHEMA +from . import OptolinkComponent, optolink_ns, CONF_OPTOLINK_ID OptolinkNumber = optolink_ns.class_( "OptolinkNumber", number.Number, cg.PollingComponent ) -CONF_OPTOLINK_ID = "optolink_id" CONFIG_SCHEMA = ( number.NUMBER_SCHEMA.extend( { diff --git a/esphome/components/optolink/optolink.cpp b/esphome/components/optolink/optolink.cpp index 3fbad9732d..b2081831ea 100644 --- a/esphome/components/optolink/optolink.cpp +++ b/esphome/components/optolink/optolink.cpp @@ -1,13 +1,19 @@ #include "esphome/core/defines.h" -#include "esphome/components/optolink/optolink.h" -#include +#include "optolink.h" +#include "VitoWiFi.h" -VitoWiFiClass VitoWiFi; +#if defined(VITOWIFI_PROTOCOL) +// NOLINTNEXTLINE +VitoWiFiClass VitoWiFi; // VITOWIFI_PROTOCOL always is set +#else +// NOLINTNEXTLINE +VitoWiFiClass VitoWiFi; // this is not really a fallback but dedicated to clang-lint +#endif namespace esphome { namespace optolink { -void Optolink::_comm() { +void Optolink::comm_() { ESP_LOGD("Optolink", "enter _comm"); VitoWiFi.readAll(); ESP_LOGD("Optolink", "exit _comm"); @@ -32,11 +38,11 @@ void Optolink::setup() { void Optolink::loop() { VitoWiFi.loop(); } -void Optolink::set_error(const std::string &format, ...) { +void Optolink::set_error(const char *format, ...) { va_list args; va_start(args, format); char buffer[128]; - size_t n = std::vsnprintf(buffer, sizeof(buffer), format.c_str(), args); + std::vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); error_ = buffer; @@ -49,12 +55,12 @@ void Optolink::read_value(IDatapoint *datapoint) { } } -void Optolink::write_value(IDatapoint *datapoint, DPValue dpValue) { +void Optolink::write_value(IDatapoint *datapoint, DPValue dp_value) { if (datapoint != nullptr) { char buffer[64]; - dpValue.getString(buffer, sizeof(buffer)); + dp_value.getString(buffer, sizeof(buffer)); ESP_LOGI("Optolink", " write value %s of datapoint %s", buffer, datapoint->getName()); - VitoWiFi.writeDatapoint(*datapoint, dpValue); + VitoWiFi.writeDatapoint(*datapoint, dp_value); } } diff --git a/esphome/components/optolink/optolink.h b/esphome/components/optolink/optolink.h old mode 100755 new mode 100644 index 3e64c62de4..fe4eb396cd --- a/esphome/components/optolink/optolink.h +++ b/esphome/components/optolink/optolink.h @@ -4,19 +4,11 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/text_sensor/text_sensor.h" -#include - -using namespace esphome; -using namespace sensor; -using namespace binary_sensor; -using namespace text_sensor; +#include "VitoWiFi.h" namespace esphome { namespace optolink { -// '00' ='WW' '01' ='RED' '02' ='NORM' '03' ='H+WW' '04' ='H+WW FS' '05' ='ABSCHALT' - -//===================================================================================================================== class Optolink : public esphome::Component, public Print { protected: std::string error_ = "OK"; @@ -25,7 +17,7 @@ class Optolink : public esphome::Component, public Print { int rx_pin_; int tx_pin_; - void _comm(); + void comm_(); public: void setup() override; @@ -38,10 +30,10 @@ 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 dpValue); + void write_value(IDatapoint *datapoint, DPValue dp_value); void read_value(IDatapoint *datapoint); - void set_error(const std::string &format, ...); + void set_error(const char *format, ...); std::string get_error() { return error_; } }; diff --git a/esphome/components/optolink/optolink_binary_sensor.h b/esphome/components/optolink/optolink_binary_sensor.h index 22d8dd5878..1aa20b9208 100644 --- a/esphome/components/optolink/optolink_binary_sensor.h +++ b/esphome/components/optolink/optolink_binary_sensor.h @@ -3,7 +3,7 @@ #include "esphome/components/binary_sensor/binary_sensor.h" #include "optolink.h" #include "optolink_sensor_base.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { @@ -18,7 +18,7 @@ class OptolinkBinarySensor : public OptolinkSensorBase, } protected: - void setup() override { setup_datapoint(); } + void setup() override { setup_datapoint_(); } void update() override { optolink_->read_value(datapoint_); } const std::string &get_sensor_name() override { return get_name(); } diff --git a/esphome/components/optolink/optolink_device_info_sensor.cpp b/esphome/components/optolink/optolink_device_info_sensor.cpp new file mode 100644 index 0000000000..5fad8ec52b --- /dev/null +++ b/esphome/components/optolink/optolink_device_info_sensor.cpp @@ -0,0 +1,22 @@ +#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 diff --git a/esphome/components/optolink/optolink_device_info_sensor.h b/esphome/components/optolink/optolink_device_info_sensor.h index 7611459807..3555cd4215 100644 --- a/esphome/components/optolink/optolink_device_info_sensor.h +++ b/esphome/components/optolink/optolink_device_info_sensor.h @@ -3,14 +3,14 @@ #include "esphome/components/text_sensor/text_sensor.h" #include "optolink.h" #include "optolink_sensor_base.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { class OptolinkDeviceInfoSensor : public esphome::text_sensor::TextSensor, public esphome::PollingComponent { public: - OptolinkDeviceInfoSensor(std::string name, Optolink *optolink) { + OptolinkDeviceInfoSensor(const std::string &name, Optolink *optolink) { optolink_ = optolink; set_name(name); set_update_interval(1800000); @@ -18,20 +18,7 @@ class OptolinkDeviceInfoSensor : public esphome::text_sensor::TextSensor, public } protected: - void setup() override { - datapoint_ = new Datapoint(get_name().c_str(), "optolink", 0x00f8, false); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dpValue) { - uint32_t value = dpValue.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); - }); - } + void setup() override; void update() override { optolink_->read_value(datapoint_); } private: diff --git a/esphome/components/optolink/optolink_number.cpp b/esphome/components/optolink/optolink_number.cpp index 1032645009..5c071284f2 100644 --- a/esphome/components/optolink/optolink_number.cpp +++ b/esphome/components/optolink/optolink_number.cpp @@ -1,6 +1,6 @@ #include "optolink_number.h" #include "optolink.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { @@ -11,7 +11,7 @@ void OptolinkNumber::control(float value) { 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); + update_datapoint_(value); publish_state(value); } }; diff --git a/esphome/components/optolink/optolink_number.h b/esphome/components/optolink/optolink_number.h index 2cc73a46db..5868eddccd 100644 --- a/esphome/components/optolink/optolink_number.h +++ b/esphome/components/optolink/optolink_number.h @@ -3,7 +3,7 @@ #include "esphome/components/number/number.h" #include "optolink_sensor_base.h" #include "optolink.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { @@ -13,7 +13,7 @@ class OptolinkNumber : public OptolinkSensorBase, public esphome::number::Number OptolinkNumber(Optolink *optolink) : OptolinkSensorBase(optolink, true) {} protected: - void setup() override { setup_datapoint(); } + void setup() override { setup_datapoint_(); } void update() override { optolink_->read_value(datapoint_); } const std::string &get_sensor_name() override { return get_name(); } diff --git a/esphome/components/optolink/optolink_select.cpp b/esphome/components/optolink/optolink_select.cpp index f7c53077f6..3831984ae7 100644 --- a/esphome/components/optolink/optolink_select.cpp +++ b/esphome/components/optolink/optolink_select.cpp @@ -1,6 +1,6 @@ #include "optolink_select.h" #include "optolink.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { @@ -9,7 +9,7 @@ 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)); + update_datapoint_(std::stof(it->first)); publish_state(it->second); break; } diff --git a/esphome/components/optolink/optolink_select.h b/esphome/components/optolink/optolink_select.h index 63b379c1e5..454230fb0e 100644 --- a/esphome/components/optolink/optolink_select.h +++ b/esphome/components/optolink/optolink_select.h @@ -4,7 +4,7 @@ #include "esphome/components/select/select.h" #include "optolink.h" #include "optolink_sensor_base.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { @@ -16,14 +16,14 @@ class OptolinkSelect : public OptolinkSensorBase, public esphome::select::Select void set_map(std::map *mapping) { mapping_ = mapping; std::vector values; - for (auto it = mapping->begin(); it != mapping->end(); ++it) { - values.push_back(it->second); + for (auto &it : *mapping) { + values.push_back(it.second); } traits.set_options(values); }; protected: - void setup() override { setup_datapoint(); } + void setup() override { setup_datapoint_(); } void update() override { optolink_->read_value(datapoint_); } const std::string &get_sensor_name() override { return get_name(); } diff --git a/esphome/components/optolink/optolink_sensor.h b/esphome/components/optolink/optolink_sensor.h index b8613fc470..dd10697de2 100644 --- a/esphome/components/optolink/optolink_sensor.h +++ b/esphome/components/optolink/optolink_sensor.h @@ -3,7 +3,7 @@ #include "esphome/components/sensor/sensor.h" #include "optolink.h" #include "optolink_sensor_base.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { @@ -15,7 +15,7 @@ class OptolinkSensor : public OptolinkSensorBase, public esphome::sensor::Sensor } protected: - void setup() { setup_datapoint(); } + void setup() { setup_datapoint_(); } void update() override { optolink_->read_value(datapoint_); } const std::string &get_sensor_name() override { return get_name(); } diff --git a/esphome/components/optolink/optolink_sensor_base.cpp b/esphome/components/optolink/optolink_sensor_base.cpp index 599f4b791d..26ea2ed000 100644 --- a/esphome/components/optolink/optolink_sensor_base.cpp +++ b/esphome/components/optolink/optolink_sensor_base.cpp @@ -4,7 +4,7 @@ namespace esphome { namespace optolink { -void OptolinkSensorBase::update_datapoint(float value) { +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()); @@ -31,8 +31,6 @@ void OptolinkSensorBase::update_datapoint(float value) { optolink_->write_value(datapoint_, DPValue((uint16_t) value)); break; case 10: - optolink_->write_value(datapoint_, DPValue((float) value)); - break; case 100: optolink_->write_value(datapoint_, DPValue((float) value)); break; @@ -66,22 +64,22 @@ void OptolinkSensorBase::update_datapoint(float value) { } } -void OptolinkSensorBase::setup_datapoint() { +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 dpValue) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %d", dp.getGroup(), dp.getName(), dpValue.getU8()); - value_changed(dpValue.getU8()); + 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 dpValue) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dpValue.getFloat()); - value_changed(dpValue.getFloat()); + 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: @@ -94,23 +92,23 @@ void OptolinkSensorBase::setup_datapoint() { switch (div_ratio_) { case 1: datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dpValue) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %d", dp.getGroup(), dp.getName(), dpValue.getU16()); - value_changed(dpValue.getU16()); + 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 dpValue) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dpValue.getFloat()); - value_changed(dpValue.getFloat()); + 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 dpValue) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dpValue.getFloat()); - value_changed(dpValue.getFloat()); + 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: @@ -123,16 +121,16 @@ void OptolinkSensorBase::setup_datapoint() { switch (div_ratio_) { case 1: datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dpValue) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %d", dp.getGroup(), dp.getName(), dpValue.getU32()); - value_changed(dpValue.getU32()); + 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 dpValue) { - ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: %f", dp.getGroup(), dp.getName(), dpValue.getFloat()); - value_changed(dpValue.getFloat()); + 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: @@ -153,6 +151,7 @@ void conv2_100_F::encode(uint8_t *out, DPValue in) { 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); diff --git a/esphome/components/optolink/optolink_sensor_base.h b/esphome/components/optolink/optolink_sensor_base.h index 93074ea2f2..acb6992d94 100644 --- a/esphome/components/optolink/optolink_sensor_base.h +++ b/esphome/components/optolink/optolink_sensor_base.h @@ -2,7 +2,7 @@ #pragma once #include "esphome/core/log.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { @@ -18,8 +18,8 @@ class OptolinkSensorBase { int bytes_; int div_ratio_ = 1; - void setup_datapoint(); - void update_datapoint(float value); + void setup_datapoint_(); + void update_datapoint_(float value); public: OptolinkSensorBase(Optolink *optolink, bool writeable = false) { @@ -36,11 +36,12 @@ class OptolinkSensorBase { 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); - const size_t getLength() const { return 2; } + size_t get_length() const { return 2; } }; } // namespace optolink diff --git a/esphome/components/optolink/optolink_state_sensor.h b/esphome/components/optolink/optolink_state_sensor.h index 78d99ac19d..324c320a5c 100644 --- a/esphome/components/optolink/optolink_state_sensor.h +++ b/esphome/components/optolink/optolink_state_sensor.h @@ -3,7 +3,7 @@ #include "esphome/components/text_sensor/text_sensor.h" #include "optolink.h" #include "optolink_sensor_base.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { diff --git a/esphome/components/optolink/optolink_switch.cpp b/esphome/components/optolink/optolink_switch.cpp index 226dd3d484..514ae5a677 100644 --- a/esphome/components/optolink/optolink_switch.cpp +++ b/esphome/components/optolink/optolink_switch.cpp @@ -1,6 +1,6 @@ #include "optolink_switch.h" #include "optolink.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { @@ -11,7 +11,7 @@ void OptolinkSwitch::write_state(bool value) { 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); + update_datapoint_(value); publish_state(value); } }; diff --git a/esphome/components/optolink/optolink_switch.h b/esphome/components/optolink/optolink_switch.h index faf283dacf..33378b59a0 100644 --- a/esphome/components/optolink/optolink_switch.h +++ b/esphome/components/optolink/optolink_switch.h @@ -3,7 +3,7 @@ #include "esphome/components/switch/switch.h" #include "optolink_sensor_base.h" #include "optolink.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { @@ -16,13 +16,13 @@ class OptolinkSwitch : public OptolinkSensorBase, public esphome::switch_::Switc } protected: - void setup() override { setup_datapoint(); } + void setup() override { setup_datapoint_(); } void update() override { optolink_->read_value(datapoint_); } const std::string &get_sensor_name() override { return get_name(); } void value_changed(float state) override { publish_state(state); }; - void write_state(bool state) override; + void write_state(bool value) override; }; } // namespace optolink diff --git a/esphome/components/optolink/optolink_text_sensor.cpp b/esphome/components/optolink/optolink_text_sensor.cpp index f226f4f5d0..4f6f656aca 100644 --- a/esphome/components/optolink/optolink_text_sensor.cpp +++ b/esphome/components/optolink/optolink_text_sensor.cpp @@ -1,20 +1,20 @@ #include "optolink_text_sensor.h" #include "optolink.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { void OptolinkTextSensor::setup() { if (!raw_) { - setup_datapoint(); + setup_datapoint_(); } else { datapoint_ = new Datapoint(get_sensor_name().c_str(), "optolink", address_, writeable_); datapoint_->setLength(bytes_); - datapoint_->setCallback([this](const IDatapoint &dp, DPValue dpValue) { + datapoint_->setCallback([this](const IDatapoint &dp, DPValue dp_value) { ESP_LOGD("OptolinkSensorBase", "Datapoint %s - %s: ", dp.getGroup(), dp.getName()); uint8_t buffer[bytes_ + 1]; - dpValue.getRaw(buffer); + dp_value.getRaw(buffer); buffer[bytes_] = 0x0; publish_state((char *) buffer); }); diff --git a/esphome/components/optolink/optolink_text_sensor.h b/esphome/components/optolink/optolink_text_sensor.h index bb181c3965..56d7c0464a 100644 --- a/esphome/components/optolink/optolink_text_sensor.h +++ b/esphome/components/optolink/optolink_text_sensor.h @@ -3,7 +3,7 @@ #include "esphome/components/text_sensor/text_sensor.h" #include "optolink.h" #include "optolink_sensor_base.h" -#include +#include "VitoWiFi.h" namespace esphome { namespace optolink { diff --git a/esphome/components/optolink/select.py b/esphome/components/optolink/select.py index 34c3482206..5d6aa2ff85 100644 --- a/esphome/components/optolink/select.py +++ b/esphome/components/optolink/select.py @@ -2,7 +2,6 @@ from esphome import core import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import select -from esphome.components.optolink.sensor import SENSOR_BASE_SCHEMA from esphome.const import ( CONF_ADDRESS, CONF_BYTES, @@ -12,7 +11,8 @@ from esphome.const import ( CONF_TO, CONF_UPDATE_INTERVAL, ) -from . import OptolinkComponent, optolink_ns +from . import OptolinkComponent, optolink_ns, CONF_OPTOLINK_ID +from .sensor import SENSOR_BASE_SCHEMA OptolinkSelect = optolink_ns.class_( "OptolinkSelect", select.Select, cg.PollingComponent @@ -32,7 +32,6 @@ def validate_mapping(value): )(value) -CONF_OPTOLINK_ID = "optolink_id" CONF_MAP = "map" MAP_ID = "mappings" CONFIG_SCHEMA = ( diff --git a/esphome/components/optolink/text_sensor.py b/esphome/components/optolink/text_sensor.py index 662474236e..09280c91aa 100644 --- a/esphome/components/optolink/text_sensor.py +++ b/esphome/components/optolink/text_sensor.py @@ -1,6 +1,5 @@ from esphome import core import esphome.codegen as cg -from esphome.components.optolink.sensor import SENSOR_BASE_SCHEMA import esphome.config_validation as cv from esphome.components import text_sensor from esphome.const import ( @@ -11,13 +10,13 @@ from esphome.const import ( CONF_RAW, CONF_UPDATE_INTERVAL, ) -from . import optolink_ns, OptolinkComponent +from . import optolink_ns, OptolinkComponent, CONF_OPTOLINK_ID +from .sensor import SENSOR_BASE_SCHEMA OptolinkTextSensor = optolink_ns.class_( "OptolinkTextSensor", text_sensor.TextSensor, cg.PollingComponent ) -CONF_OPTOLINK_ID = "optolink_id" CONFIG_SCHEMA = cv.All( text_sensor.text_sensor_schema(OptolinkTextSensor) .extend( diff --git a/platformio.ini b/platformio.ini index 3df6446ff1..06823d5dd2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -65,6 +65,7 @@ lib_deps = rweather/Crypto@0.4.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea tonia/HeatpumpIR@1.0.20 ; heatpumpir + bertmelis/VitoWiFi@1.0.2 ; optolink build_flags = ${common.build_flags} -DUSE_ARDUINO From d49ed3edcf796d3fb918fca76ee2dc7bf427556c Mon Sep 17 00:00:00 2001 From: jota29 Date: Sat, 11 Mar 2023 23:34:51 +0000 Subject: [PATCH 10/10] added tests --- tests/test1.yaml | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/test1.yaml b/tests/test1.yaml index ef389ea712..2aa86e2fc4 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -323,6 +323,14 @@ mcp23s17: cs_pin: GPIO12 deviceaddress: 1 +optolink: + protocol: P300 + rx_pin: GPIO15 + tx_pin: GPIO16 + logger: true + device_info: Device Info + state: Component state + sensor: - platform: internal_temperature name: Internal Temperature @@ -1259,6 +1267,13 @@ sensor: name: DHT Absolute Humidity temperature: dht_temperature humidity: dht_humidity + - platform: optolink + name: Boiler Temperature + address: 0xA309 + bytes: 2 + div_ratio: 100 + unit_of_measurement: °C + device_class: temperature esp32_touch: setup_mode: false @@ -1532,6 +1547,9 @@ binary_sensor: name: movement has_still_target: name: still + - platform: optolink + name: Disturbance + address: 0x0A82 pca9685: frequency: 500 @@ -2461,6 +2479,10 @@ switch: id: outlet_switch optimistic: true device_class: outlet + - platform: optolink + name: Economy mode + address: 0x2302 + icon: mdi:sprout-outline fan: - platform: binary @@ -3032,6 +3054,11 @@ text_sensor: tag_name: OPTARIF name: optarif teleinfo_id: myteleinfo + - platform: optolink + name: Error history 1 + address: 0x7590 + bytes: 9 + raw: true sn74hc595: - id: sn74hc595_hub @@ -3136,6 +3163,17 @@ number: step: 1 max_value: 10 optimistic: true + - platform: optolink + name: Room Temperature Setpoint + unit_of_measurement: °C + address: 0x2306 + bytes: 1 + min_value: 3 + max_value: 37 + step: 1 + mode: box + icon: "mdi:home-thermometer" + device_class: temperature select: - platform: template @@ -3149,6 +3187,14 @@ select: - platform: copy source_id: test_select name: Test Select Copy + - platform: optolink + name: Operation mode + address: 0x2323 + bytes: 1 + map: + - "0 -> Off" + - "1 -> Only hot water" + - "2 -> Heating and hot water" qr_code: - id: homepage_qr