From ee4f91cea293fdfe6431efc2a252821c9cbd9db6 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 22 May 2021 16:08:57 +0200 Subject: [PATCH 1/6] Compatiblity fix for new coroutine setup in ESPHome. --- components/xiaomi_bslamp2/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/xiaomi_bslamp2/__init__.py b/components/xiaomi_bslamp2/__init__.py index ca71f2e..0799972 100644 --- a/components/xiaomi_bslamp2/__init__.py +++ b/components/xiaomi_bslamp2/__init__.py @@ -65,14 +65,14 @@ CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({ @coroutine def make_gpio(number, mode="OUTPUT"): - yield from cg.gpio_pin_expression({ "number": number, "mode": mode }); + yield cg.gpio_pin_expression({ "number": number, "mode": mode }); @coroutine def make_gpio_binary_output(id_, number): gpio_var = yield make_gpio(number) output_var = cg.new_Pvariable(id_) cg.add(output_var.set_pin(gpio_var)) - yield from cg.register_component(output_var, {}) + yield cg.register_component(output_var, {}) @coroutine def make_ledc_output(id_, number, frequency, channel): @@ -80,7 +80,7 @@ def make_ledc_output(id_, number, frequency, channel): ledc_var = cg.new_Pvariable(id_, gpio_var) cg.add(ledc_var.set_frequency(frequency)); cg.add(ledc_var.set_channel(channel)); - yield from cg.register_component(ledc_var, {}) + yield cg.register_component(ledc_var, {}) @coroutine def make_light_hal(config): From 22f3d58e79cbcd18d8c3032f4d0cbed6305af8d8 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 22 May 2021 16:49:40 +0200 Subject: [PATCH 2/6] Switched to async/await code generation. --- CHANGELOG.md | 6 ++++ components/xiaomi_bslamp2/__init__.py | 45 ++++++++++++--------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1457542..fa13ff1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Not yet released] + +### Changed +- Made the codebase compatible with ESPHome v1.x.0 + ([PR #1657: Introduce new async-def coroutine syntax](https://github.com/esphome/esphome/pull/1657)) + ## [1.0.0] **Note**: This release requires ESPHome v1.18.0 or newer. diff --git a/components/xiaomi_bslamp2/__init__.py b/components/xiaomi_bslamp2/__init__.py index 0799972..16f0dbc 100644 --- a/components/xiaomi_bslamp2/__init__.py +++ b/components/xiaomi_bslamp2/__init__.py @@ -63,35 +63,31 @@ CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend({ ), }) -@coroutine -def make_gpio(number, mode="OUTPUT"): - yield cg.gpio_pin_expression({ "number": number, "mode": mode }); +async def make_gpio(number, mode="OUTPUT"): + return await cg.gpio_pin_expression({ "number": number, "mode": mode }); -@coroutine -def make_gpio_binary_output(id_, number): - gpio_var = yield make_gpio(number) +async def make_gpio_binary_output(id_, number): + gpio_var = await make_gpio(number) output_var = cg.new_Pvariable(id_) cg.add(output_var.set_pin(gpio_var)) - yield cg.register_component(output_var, {}) + return await cg.register_component(output_var, {}) -@coroutine -def make_ledc_output(id_, number, frequency, channel): - gpio_var = yield make_gpio(number) +async def make_ledc_output(id_, number, frequency, channel): + gpio_var = await make_gpio(number) ledc_var = cg.new_Pvariable(id_, gpio_var) cg.add(ledc_var.set_frequency(frequency)); cg.add(ledc_var.set_channel(channel)); - yield cg.register_component(ledc_var, {}) + return await cg.register_component(ledc_var, {}) -@coroutine -def make_light_hal(config): - r_var = yield make_ledc_output(config[CONF_RED_ID], config[CONF_RED], 3000, 0) - g_var = yield make_ledc_output(config[CONF_GREEN_ID], config[CONF_GREEN], 3000, 1) - b_var = yield make_ledc_output(config[CONF_BLUE_ID], config[CONF_BLUE], 3000, 2) - w_var = yield make_ledc_output(config[CONF_WHITE_ID], config[CONF_WHITE], 10000, 4) - m1_var = yield make_gpio_binary_output(config[CONF_MASTER1_ID], config[CONF_MASTER1]) - m2_var = yield make_gpio_binary_output(config[CONF_MASTER2_ID], config[CONF_MASTER2]) +async def make_light_hal(config): + r_var = await make_ledc_output(config[CONF_RED_ID], config[CONF_RED], 3000, 0) + g_var = await make_ledc_output(config[CONF_GREEN_ID], config[CONF_GREEN], 3000, 1) + b_var = await make_ledc_output(config[CONF_BLUE_ID], config[CONF_BLUE], 3000, 2) + w_var = await make_ledc_output(config[CONF_WHITE_ID], config[CONF_WHITE], 10000, 4) + m1_var = await make_gpio_binary_output(config[CONF_MASTER1_ID], config[CONF_MASTER1]) + m2_var = await make_gpio_binary_output(config[CONF_MASTER2_ID], config[CONF_MASTER2]) light_hal = cg.new_Pvariable(config[CONF_LIGHT_HAL_ID]) - yield cg.register_component(light_hal, config) + await cg.register_component(light_hal, config) cg.add(light_hal.set_red_pin(r_var)) cg.add(light_hal.set_green_pin(g_var)) cg.add(light_hal.set_blue_pin(b_var)) @@ -99,17 +95,16 @@ def make_light_hal(config): cg.add(light_hal.set_master1_pin(m1_var)) cg.add(light_hal.set_master2_pin(m2_var)) -@coroutine -def make_front_panel_hal(config): - trigger_pin = yield make_gpio(config[CONF_TRIGGER_PIN], "INPUT") +async def make_front_panel_hal(config): + trigger_pin = await make_gpio(config[CONF_TRIGGER_PIN], "INPUT") fp_hal = cg.new_Pvariable(config[CONF_FRONT_PANEL_HAL_ID]) - yield cg.register_component(fp_hal, config) + await cg.register_component(fp_hal, config) cg.add(fp_hal.set_trigger_pin(trigger_pin)) # The i2c component automatically sets up one I2C bus. # Take that bus and update is to make it work for the # front panel I2C communication. - fp_i2c_var = yield cg.get_variable(config[CONF_FP_I2C_ID]) + fp_i2c_var = await cg.get_variable(config[CONF_FP_I2C_ID]) cg.add(fp_i2c_var.set_sda_pin(config[CONF_SDA])) cg.add(fp_i2c_var.set_scl_pin(config[CONF_SCL])) cg.add(fp_i2c_var.set_scan(True)) From d6d52dc9b8f5637d66b02106e0c940830c08063c Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 22 May 2021 17:55:10 +0200 Subject: [PATCH 3/6] Made to_code() fully async-compatible. --- components/xiaomi_bslamp2/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/xiaomi_bslamp2/__init__.py b/components/xiaomi_bslamp2/__init__.py index 16f0dbc..a804686 100644 --- a/components/xiaomi_bslamp2/__init__.py +++ b/components/xiaomi_bslamp2/__init__.py @@ -111,12 +111,12 @@ async def make_front_panel_hal(config): cg.add(fp_hal.set_i2c_parent(fp_i2c_var)) cg.add(fp_hal.set_i2c_address(config[CONF_ADDRESS])) -def to_code(config): +async def to_code(config): # Dirty little hack to make the ESPHome component loader include # the code for the "gpio" platform for the "output" domain. # Loading specific platform components is not possible using # the AUTO_LOAD feature unfortunately. CORE.config["output"].append({ CONF_PLATFORM: "gpio" }) - yield make_light_hal(config) - yield make_front_panel_hal(config) + await make_light_hal(config) + await make_front_panel_hal(config) From 915643a87d2f9e021f6b3a65079278f3652daaad Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 22 May 2021 18:17:39 +0200 Subject: [PATCH 4/6] Added visual feedback during OTA upgrades. --- CHANGELOG.md | 6 +++ components/ota_triggers/__init__.py | 70 ++++++++++++++++++++++++++++ components/ota_triggers/automation.h | 56 ++++++++++++++++++++++ example.yaml | 41 ++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 components/ota_triggers/__init__.py create mode 100644 components/ota_triggers/automation.h diff --git a/CHANGELOG.md b/CHANGELOG.md index fa13ff1..5cfef16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Made the codebase compatible with ESPHome v1.x.0 ([PR #1657: Introduce new async-def coroutine syntax](https://github.com/esphome/esphome/pull/1657)) +### Added +- Implemented support for visual feedback during the OTA flashing process in the + `example.yaml` file: the light becomes blue during the process, the brightness bar + represents the update progress, when updating fails the light flashes red and when it + completes successfuly, the light flashes green. + ## [1.0.0] **Note**: This release requires ESPHome v1.18.0 or newer. diff --git a/components/ota_triggers/__init__.py b/components/ota_triggers/__init__.py new file mode 100644 index 0000000..e2880ca --- /dev/null +++ b/components/ota_triggers/__init__.py @@ -0,0 +1,70 @@ +from esphome.cpp_generator import RawExpression +from esphome import automation +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components.ota import ota_ns, OTAComponent +from esphome.const import ( + CONF_ID, +) + +CODEOWNERS = ["@mmakaay"] +DEPENDENCIES = ["ota"] + +CONF_ON_BEGIN = "on_begin" +CONF_ON_PROGRESS = "on_progress" +CONF_ON_END = "on_end" +CONF_ON_ERROR = "on_error" +CONF_ON_BEGIN_TRIGGER_ID = "on_begin_trigger_id" +CONF_ON_PROGRESS_TRIGGER_ID = "on_progress_trigger_id" +CONF_ON_END_TRIGGER_ID = "on_end_trigger_id" +CONF_ON_ERROR_TRIGGER_ID = "on_error_trigger_id" + +OTAStartTrigger = ota_ns.class_("OTAStartTrigger", automation.Trigger.template()) +OTAProgressTrigger = ota_ns.class_("OTAProgressTrigger", automation.Trigger.template()) +OTAEndTrigger = ota_ns.class_("OTAEndTrigger", automation.Trigger.template()) +OTAErrorTrigger = ota_ns.class_("OTAErrorTrigger", automation.Trigger.template()) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(OTAComponent), + cv.Optional(CONF_ON_BEGIN): automation.validate_automation( + { + cv.GenerateID(CONF_ON_BEGIN_TRIGGER_ID): cv.declare_id(OTAStartTrigger), + } + ), + cv.Optional(CONF_ON_ERROR): automation.validate_automation( + { + cv.GenerateID(CONF_ON_ERROR_TRIGGER_ID): cv.declare_id(OTAErrorTrigger), + } + ), + cv.Optional(CONF_ON_PROGRESS): automation.validate_automation( + { + cv.GenerateID(CONF_ON_PROGRESS_TRIGGER_ID): cv.declare_id( + OTAProgressTrigger + ), + } + ), + cv.Optional(CONF_ON_END): automation.validate_automation( + { + cv.GenerateID(CONF_ON_END_TRIGGER_ID): cv.declare_id(OTAEndTrigger), + } + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = await cg.get_variable(config[CONF_ID]) + + for conf in config.get(CONF_ON_BEGIN, []): + trigger = cg.new_Pvariable(conf[CONF_ON_BEGIN_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_PROGRESS, []): + trigger = cg.new_Pvariable(conf[CONF_ON_PROGRESS_TRIGGER_ID], var) + await automation.build_automation(trigger, [(float, "x")], conf) + for conf in config.get(CONF_ON_END, []): + trigger = cg.new_Pvariable(conf[CONF_ON_END_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_ERROR, []): + trigger = cg.new_Pvariable(conf[CONF_ON_ERROR_TRIGGER_ID], var) + await automation.build_automation(trigger, [(int, "x")], conf) diff --git a/components/ota_triggers/automation.h b/components/ota_triggers/automation.h new file mode 100644 index 0000000..3277658 --- /dev/null +++ b/components/ota_triggers/automation.h @@ -0,0 +1,56 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/ota/ota_component.h" + +namespace esphome { +namespace ota { + +class OTAStartTrigger : public Trigger<> { + public: + explicit OTAStartTrigger(OTAComponent *parent) { + parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { + if (state == OTAState::Started && !parent->is_failed()) { + trigger(); + } + }); + } +}; + +class OTAProgressTrigger : public Trigger { + public: + explicit OTAProgressTrigger(OTAComponent *parent) { + parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { + if (state == OTAState::InProgress && !parent->is_failed()) { + trigger(progress); + } + }); + } +}; + +class OTAEndTrigger : public Trigger<> { + public: + explicit OTAEndTrigger(OTAComponent *parent) { + parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { + if (state == OTAState::Completed && !parent->is_failed()) { + trigger(); + } + }); + } +}; + + +class OTAErrorTrigger : public Trigger { + public: + explicit OTAErrorTrigger(OTAComponent *parent) { + parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { + if (state == OTAState::Error && !parent->is_failed()) { + trigger(error); + } + }); + } +}; + +} // namespace ota +} // namespace esphome diff --git a/example.yaml b/example.yaml index 7f5cf14..872eaa2 100644 --- a/example.yaml +++ b/example.yaml @@ -200,3 +200,44 @@ sensor: id: ${id_light} brightness: !lambda return x; +# These OTA triggers are used to provide some visual feedback during the OTA +# flashing process. The light is turned blue when the upgrade starts, the +# brightness indicator will represent the update progress (fills up from 0% +# to 100%), the light will flash red when the upgrade fails or green when the +# upgrade succeeds. +# You can safely remove this section if you don't want the visual feedback. +ota_triggers: + on_begin: + then: + - light.disco_on: + id: ${id_light} + red: 0% + green: 0% + blue: 100% + brightness: 2% + transition_length: 0s + on_progress: + then: + - output.set_level: + id: ${id_front_panel_illumination} + level: !lambda return (x / 100.0f); + on_end: + then: + - light.disco_on: + id: ${id_light} + red: 0% + green: 100% + blue: 0% + brightness: 2% + transition_length: 0s + on_error: + then: + - light.disco_on: + id: ${id_light} + red: 100% + green: 0% + blue: 0% + brightness: 2% + - delay: 1s + - light.disco_off: + id: ${id_light} From 35d7f20f8eb6086e99fc4a097c85f5e10fb314eb Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 26 May 2021 17:17:16 +0200 Subject: [PATCH 5/6] Updated CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cfef16..e597a42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Not yet released] ### Changed +- Fixed a rounding error in the slider sensor component. When using custom "range from" / "range to" + settings, the maximum value could exceed the "range to" value due to rounding errors. + Thanks to Jos for the heads up! - Made the codebase compatible with ESPHome v1.x.0 ([PR #1657: Introduce new async-def coroutine syntax](https://github.com/esphome/esphome/pull/1657)) From d0dde2d63792a96a10c790b12a1e0f89315da244 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 27 May 2021 14:44:12 +0200 Subject: [PATCH 6/6] Updated name of light to '...RGBWW Light' instead of '...RGBW Light'. --- example.yaml | 84 ++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/example.yaml b/example.yaml index 872eaa2..6a316d6 100644 --- a/example.yaml +++ b/example.yaml @@ -42,6 +42,47 @@ api: ota: password: "Password-For-Flashing-This-Device-Over-The-Air" + # These OTA triggers are used to provide some visual feedback during the OTA + # flashing process. The light is turned blue when the upgrade starts, the + # brightness indicator will represent the update progress (fills up from 0% + # to 100%), the light will flash red when the upgrade fails or green when the + # upgrade succeeds. + # You can safely remove these if you don't want the visual feedback. + on_begin: + then: + - light.disco_on: + id: ${id_light} + red: 0% + green: 0% + blue: 100% + brightness: 2% + transition_length: 0s + on_progress: + then: + - output.set_level: + id: ${id_front_panel_illumination} + level: !lambda return (x / 100.0f); + on_end: + then: + - light.disco_on: + id: ${id_light} + red: 0% + green: 100% + blue: 0% + brightness: 2% + transition_length: 0s + on_error: + then: + - light.disco_on: + id: ${id_light} + red: 100% + green: 0% + blue: 0% + brightness: 2% + - delay: 1s + - light.disco_off: + id: ${id_light} + # The log level can be raised when needed for debugging the firmware. For # production, a low log level is recommended. Mainly because high volume log # output might interfere with the API/WiFi connection stability. So when @@ -78,7 +119,7 @@ esphome: light: - platform: xiaomi_bslamp2 id: ${id_light} - name: ${friendly_name} RGBW Light + name: ${friendly_name} RGBWW Light default_transition_length: ${transition_length} # When the brightness is changed, then update the level indicator # on the front panel accordingly. In night light mode, turn off @@ -200,44 +241,3 @@ sensor: id: ${id_light} brightness: !lambda return x; -# These OTA triggers are used to provide some visual feedback during the OTA -# flashing process. The light is turned blue when the upgrade starts, the -# brightness indicator will represent the update progress (fills up from 0% -# to 100%), the light will flash red when the upgrade fails or green when the -# upgrade succeeds. -# You can safely remove this section if you don't want the visual feedback. -ota_triggers: - on_begin: - then: - - light.disco_on: - id: ${id_light} - red: 0% - green: 0% - blue: 100% - brightness: 2% - transition_length: 0s - on_progress: - then: - - output.set_level: - id: ${id_front_panel_illumination} - level: !lambda return (x / 100.0f); - on_end: - then: - - light.disco_on: - id: ${id_light} - red: 0% - green: 100% - blue: 0% - brightness: 2% - transition_length: 0s - on_error: - then: - - light.disco_on: - id: ${id_light} - red: 100% - green: 0% - blue: 0% - brightness: 2% - - delay: 1s - - light.disco_off: - id: ${id_light}