From 1d25db491c26b7400a8483ab2199fa8d05badfbc Mon Sep 17 00:00:00 2001 From: Markus <974709+Links2004@users.noreply.github.com> Date: Wed, 14 Aug 2024 04:03:12 +0200 Subject: [PATCH 01/12] [homeassistant] Native switch entity import and control (#7018) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + .../homeassistant/switch/__init__.py | 30 ++++++++++ .../switch/homeassistant_switch.cpp | 59 +++++++++++++++++++ .../switch/homeassistant_switch.h | 22 +++++++ tests/components/homeassistant/common.yaml | 5 ++ 5 files changed, 117 insertions(+) create mode 100644 esphome/components/homeassistant/switch/__init__.py create mode 100644 esphome/components/homeassistant/switch/homeassistant_switch.cpp create mode 100644 esphome/components/homeassistant/switch/homeassistant_switch.h diff --git a/CODEOWNERS b/CODEOWNERS index 663a942cb4..0c36cda527 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -169,6 +169,7 @@ esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/hm3301/* @freekode esphome/components/homeassistant/* @OttoWinter @esphome/core +esphome/components/homeassistant/switch/* @Links2004 esphome/components/honeywell_hih_i2c/* @Benichou34 esphome/components/honeywellabp/* @RubyBailey esphome/components/honeywellabp2_i2c/* @jpfaff diff --git a/esphome/components/homeassistant/switch/__init__.py b/esphome/components/homeassistant/switch/__init__.py new file mode 100644 index 0000000000..3d7c80682a --- /dev/null +++ b/esphome/components/homeassistant/switch/__init__.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import CONF_ID + +from .. import ( + HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, + homeassistant_ns, + setup_home_assistant_entity, +) + +CODEOWNERS = ["@Links2004"] +DEPENDENCIES = ["api"] + +HomeassistantSwitch = homeassistant_ns.class_( + "HomeassistantSwitch", switch.Switch, cg.Component +) + +CONFIG_SCHEMA = ( + switch.switch_schema(HomeassistantSwitch) + .extend(cv.COMPONENT_SCHEMA) + .extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await switch.register_switch(var, config) + setup_home_assistant_entity(var, config) diff --git a/esphome/components/homeassistant/switch/homeassistant_switch.cpp b/esphome/components/homeassistant/switch/homeassistant_switch.cpp new file mode 100644 index 0000000000..05ef46e30e --- /dev/null +++ b/esphome/components/homeassistant/switch/homeassistant_switch.cpp @@ -0,0 +1,59 @@ +#include "homeassistant_switch.h" +#include "esphome/components/api/api_server.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace homeassistant { + +static const char *const TAG = "homeassistant.switch"; + +using namespace esphome::switch_; + +void HomeassistantSwitch::setup() { + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullopt, [this](const std::string &state) { + auto val = parse_on_off(state.c_str()); + switch (val) { + case PARSE_NONE: + case PARSE_TOGGLE: + ESP_LOGW(TAG, "Can't convert '%s' to binary state!", state.c_str()); + break; + case PARSE_ON: + case PARSE_OFF: + bool new_state = val == PARSE_ON; + ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), ONOFF(new_state)); + this->publish_state(new_state); + break; + } + }); +} + +void HomeassistantSwitch::dump_config() { + LOG_SWITCH("", "Homeassistant Switch", this); + ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str()); +} + +float HomeassistantSwitch::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } + +void HomeassistantSwitch::write_state(bool state) { + if (!api::global_api_server->is_connected()) { + ESP_LOGE(TAG, "No clients connected to API server"); + return; + } + + api::HomeassistantServiceResponse resp; + if (state) { + resp.service = "switch.turn_on"; + } else { + resp.service = "switch.turn_off"; + } + + api::HomeassistantServiceMap entity_id_kv; + entity_id_kv.key = "entity_id"; + entity_id_kv.value = this->entity_id_; + resp.data.push_back(entity_id_kv); + + api::global_api_server->send_homeassistant_service_call(resp); +} + +} // namespace homeassistant +} // namespace esphome diff --git a/esphome/components/homeassistant/switch/homeassistant_switch.h b/esphome/components/homeassistant/switch/homeassistant_switch.h new file mode 100644 index 0000000000..a4da257960 --- /dev/null +++ b/esphome/components/homeassistant/switch/homeassistant_switch.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace homeassistant { + +class HomeassistantSwitch : public switch_::Switch, public Component { + public: + void set_entity_id(const std::string &entity_id) { this->entity_id_ = entity_id; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + + protected: + void write_state(bool state) override; + std::string entity_id_; +}; + +} // namespace homeassistant +} // namespace esphome diff --git a/tests/components/homeassistant/common.yaml b/tests/components/homeassistant/common.yaml index 07a6e8090c..2b2805d06e 100644 --- a/tests/components/homeassistant/common.yaml +++ b/tests/components/homeassistant/common.yaml @@ -32,6 +32,11 @@ wifi: api: +switch: + - platform: homeassistant + entity_id: switch.my_cool_switch + id: my_cool_switch + binary_sensor: - platform: homeassistant entity_id: binary_sensor.hello_world From a5fdcb31fc56d928aa7a3b340829970f6ce0bbc0 Mon Sep 17 00:00:00 2001 From: Landon Rohatensky Date: Tue, 13 Aug 2024 19:04:12 -0700 Subject: [PATCH 02/12] [homeassistant] Native number entity import and control (#6455) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + .../homeassistant/number/__init__.py | 33 ++++++ .../number/homeassistant_number.cpp | 100 ++++++++++++++++++ .../number/homeassistant_number.h | 31 ++++++ tests/components/homeassistant/common.yaml | 5 + 5 files changed, 170 insertions(+) create mode 100644 esphome/components/homeassistant/number/__init__.py create mode 100644 esphome/components/homeassistant/number/homeassistant_number.cpp create mode 100644 esphome/components/homeassistant/number/homeassistant_number.h diff --git a/CODEOWNERS b/CODEOWNERS index 0c36cda527..3ea9c75ac2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -169,6 +169,7 @@ esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/hm3301/* @freekode esphome/components/homeassistant/* @OttoWinter @esphome/core +esphome/components/homeassistant/number/* @landonr esphome/components/homeassistant/switch/* @Links2004 esphome/components/honeywell_hih_i2c/* @Benichou34 esphome/components/honeywellabp/* @RubyBailey diff --git a/esphome/components/homeassistant/number/__init__.py b/esphome/components/homeassistant/number/__init__.py new file mode 100644 index 0000000000..a6cc615a64 --- /dev/null +++ b/esphome/components/homeassistant/number/__init__.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +from esphome.components import number +import esphome.config_validation as cv + +from .. import ( + HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, + homeassistant_ns, + setup_home_assistant_entity, +) + +CODEOWNERS = ["@landonr"] +DEPENDENCIES = ["api"] + +HomeassistantNumber = homeassistant_ns.class_( + "HomeassistantNumber", number.Number, cg.Component +) + +CONFIG_SCHEMA = ( + number.number_schema(HomeassistantNumber) + .extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await number.new_number( + config, + min_value=0, + max_value=0, + step=0, + ) + await cg.register_component(var, config) + setup_home_assistant_entity(var, config) diff --git a/esphome/components/homeassistant/number/homeassistant_number.cpp b/esphome/components/homeassistant/number/homeassistant_number.cpp new file mode 100644 index 0000000000..d3e285f4ac --- /dev/null +++ b/esphome/components/homeassistant/number/homeassistant_number.cpp @@ -0,0 +1,100 @@ +#include "homeassistant_number.h" + +#include "esphome/components/api/api_pb2.h" +#include "esphome/components/api/api_server.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace homeassistant { + +static const char *const TAG = "homeassistant.number"; + +void HomeassistantNumber::state_changed_(const std::string &state) { + auto number_value = parse_number(state); + if (!number_value.has_value()) { + ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_.c_str(), state.c_str()); + this->publish_state(NAN); + return; + } + if (this->state == number_value.value()) { + return; + } + ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), state.c_str()); + this->publish_state(number_value.value()); +} + +void HomeassistantNumber::min_retrieved_(const std::string &min) { + auto min_value = parse_number(min); + if (!min_value.has_value()) { + ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_.c_str(), min.c_str()); + } + ESP_LOGD(TAG, "'%s': Min retrieved: %s", get_name().c_str(), min.c_str()); + this->traits.set_min_value(min_value.value()); +} + +void HomeassistantNumber::max_retrieved_(const std::string &max) { + auto max_value = parse_number(max); + if (!max_value.has_value()) { + ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_.c_str(), max.c_str()); + } + ESP_LOGD(TAG, "'%s': Max retrieved: %s", get_name().c_str(), max.c_str()); + this->traits.set_max_value(max_value.value()); +} + +void HomeassistantNumber::step_retrieved_(const std::string &step) { + auto step_value = parse_number(step); + if (!step_value.has_value()) { + ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_.c_str(), step.c_str()); + } + ESP_LOGD(TAG, "'%s': Step Retrieved %s", get_name().c_str(), step.c_str()); + this->traits.set_step(step_value.value()); +} + +void HomeassistantNumber::setup() { + api::global_api_server->subscribe_home_assistant_state( + this->entity_id_, nullopt, std::bind(&HomeassistantNumber::state_changed_, this, std::placeholders::_1)); + + api::global_api_server->get_home_assistant_state( + this->entity_id_, optional("min"), + std::bind(&HomeassistantNumber::min_retrieved_, this, std::placeholders::_1)); + api::global_api_server->get_home_assistant_state( + this->entity_id_, optional("max"), + std::bind(&HomeassistantNumber::max_retrieved_, this, std::placeholders::_1)); + api::global_api_server->get_home_assistant_state( + this->entity_id_, optional("step"), + std::bind(&HomeassistantNumber::step_retrieved_, this, std::placeholders::_1)); +} + +void HomeassistantNumber::dump_config() { + LOG_NUMBER("", "Homeassistant Number", this); + ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str()); +} + +float HomeassistantNumber::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } + +void HomeassistantNumber::control(float value) { + if (!api::global_api_server->is_connected()) { + ESP_LOGE(TAG, "No clients connected to API server"); + return; + } + + this->publish_state(value); + + api::HomeassistantServiceResponse resp; + resp.service = "number.set_value"; + + api::HomeassistantServiceMap entity_id; + entity_id.key = "entity_id"; + entity_id.value = this->entity_id_; + resp.data.push_back(entity_id); + + api::HomeassistantServiceMap entity_value; + entity_value.key = "value"; + entity_value.value = to_string(value); + resp.data.push_back(entity_value); + + api::global_api_server->send_homeassistant_service_call(resp); +} + +} // namespace homeassistant +} // namespace esphome diff --git a/esphome/components/homeassistant/number/homeassistant_number.h b/esphome/components/homeassistant/number/homeassistant_number.h new file mode 100644 index 0000000000..0860b4e91c --- /dev/null +++ b/esphome/components/homeassistant/number/homeassistant_number.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include "esphome/components/number/number.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace homeassistant { + +class HomeassistantNumber : public number::Number, public Component { + public: + void set_entity_id(const std::string &entity_id) { this->entity_id_ = entity_id; } + + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + + protected: + void state_changed_(const std::string &state); + void min_retrieved_(const std::string &min); + void max_retrieved_(const std::string &max); + void step_retrieved_(const std::string &step); + + void control(float value) override; + + std::string entity_id_; +}; +} // namespace homeassistant +} // namespace esphome diff --git a/tests/components/homeassistant/common.yaml b/tests/components/homeassistant/common.yaml index 2b2805d06e..8c9a4ad75f 100644 --- a/tests/components/homeassistant/common.yaml +++ b/tests/components/homeassistant/common.yaml @@ -46,6 +46,11 @@ binary_sensor: attribute: world id: ha_hello_world_binary_attribute +number: + - platform: homeassistant + entity_id: number.hello_world + id: ha_hello_world_number + sensor: - platform: homeassistant entity_id: sensor.hello_world From a0eff08f39c69059d74929e6c91b8b61fb7ee51f Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:05:25 +1000 Subject: [PATCH 03/12] [lvgl] Rework events to avoid feedback loops (#7262) --- esphome/components/lvgl/automation.py | 4 +-- esphome/components/lvgl/defines.py | 1 + esphome/components/lvgl/light/lvgl_light.h | 2 +- esphome/components/lvgl/lvcode.py | 6 ++++- esphome/components/lvgl/lvgl_esphome.cpp | 15 +++++++---- esphome/components/lvgl/lvgl_esphome.h | 5 +++- esphome/components/lvgl/number/__init__.py | 22 +++++++++++++--- esphome/components/lvgl/select/__init__.py | 13 ++++++++-- esphome/components/lvgl/sensor/__init__.py | 16 ++++++++++-- esphome/components/lvgl/switch/__init__.py | 6 +++-- esphome/components/lvgl/text/__init__.py | 17 ++++++++++--- .../components/lvgl/text_sensor/__init__.py | 4 ++- esphome/components/lvgl/trigger.py | 25 +++++++++++++++---- tests/components/lvgl/common.yaml | 1 + 14 files changed, 108 insertions(+), 29 deletions(-) diff --git a/esphome/components/lvgl/automation.py b/esphome/components/lvgl/automation.py index 556e286208..a39f589136 100644 --- a/esphome/components/lvgl/automation.py +++ b/esphome/components/lvgl/automation.py @@ -17,6 +17,7 @@ from .defines import ( from .lv_validation import lv_bool, lv_color, lv_image from .lvcode import ( LVGL_COMP_ARG, + UPDATE_EVENT, LambdaContext, LocalVariable, LvConditional, @@ -30,7 +31,6 @@ from .lvcode import ( ) from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA from .types import ( - LV_EVENT, LV_STATE, LvglAction, LvglCondition, @@ -64,7 +64,7 @@ async def update_to_code(config, action_id, template_arg, args): widget.type.w_type.value_property is not None and widget.type.w_type.value_property in config ): - lv.event_send(widget.obj, LV_EVENT.VALUE_CHANGED, nullptr) + lv.event_send(widget.obj, UPDATE_EVENT, nullptr) widgets = await get_widgets(config[CONF_ID]) return await action_to_code(widgets, do_update, action_id, template_arg, args) diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index e48679996b..8f7a973722 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -470,6 +470,7 @@ CONF_TOP_LAYER = "top_layer" CONF_TOUCHSCREENS = "touchscreens" CONF_TRANSPARENCY_KEY = "transparency_key" CONF_THEME = "theme" +CONF_UPDATE_ON_RELEASE = "update_on_release" CONF_VISIBLE_ROW_COUNT = "visible_row_count" CONF_WIDGET = "widget" CONF_WIDGETS = "widgets" diff --git a/esphome/components/lvgl/light/lvgl_light.h b/esphome/components/lvgl/light/lvgl_light.h index 67372d89dd..50ae4c5327 100644 --- a/esphome/components/lvgl/light/lvgl_light.h +++ b/esphome/components/lvgl/light/lvgl_light.h @@ -38,7 +38,7 @@ class LVLight : public light::LightOutput { void set_value_(lv_color_t value) { lv_led_set_color(this->obj_, value); lv_led_on(this->obj_); - lv_event_send(this->obj_, lv_custom_event, nullptr); + lv_event_send(this->obj_, lv_api_event, nullptr); } lv_obj_t *obj_{}; optional initial_value_{}; diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index f54a032de2..6d7e364e5d 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -29,7 +29,11 @@ LvglComponent = lvgl_ns.class_("LvglComponent", cg.PollingComponent) LVGL_COMP_ARG = [(LvglComponent.operator("ptr"), LVGL_COMP)] lv_event_t_ptr = cg.global_ns.namespace("lv_event_t").operator("ptr") EVENT_ARG = [(lv_event_t_ptr, "ev")] -CUSTOM_EVENT = literal("lvgl::lv_custom_event") +# Two custom events; API_EVENT is fired when an entity is updated remotely by an API interaction; +# UPDATE_EVENT is fired when an entity is programmatically updated locally. +# VALUE_CHANGED is the event generated by LVGL when an entity's value changes through user interaction. +API_EVENT = literal("lvgl::lv_api_event") +UPDATE_EVENT = literal("lvgl::lv_update_event") def get_line_marks(value) -> list: diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 6f23c2421b..92f7a880c3 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -27,7 +27,8 @@ static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) { area->y2++; } -lv_event_code_t lv_custom_event; // NOLINT +lv_event_code_t lv_api_event; // NOLINT +lv_event_code_t lv_update_event; // NOLINT void LvglComponent::dump_config() { ESP_LOGCONFIG(TAG, "LVGL:"); } void LvglComponent::set_paused(bool paused, bool show_snow) { this->paused_ = paused; @@ -40,15 +41,18 @@ void LvglComponent::set_paused(bool paused, bool show_snow) { } void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event) { lv_obj_add_event_cb(obj, callback, event, this); - if (event == LV_EVENT_VALUE_CHANGED) { - lv_obj_add_event_cb(obj, callback, lv_custom_event, this); - } } void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2) { this->add_event_cb(obj, callback, event1); this->add_event_cb(obj, callback, event2); } +void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, + lv_event_code_t event2, lv_event_code_t event3) { + this->add_event_cb(obj, callback, event1); + this->add_event_cb(obj, callback, event2); + this->add_event_cb(obj, callback, event3); +} void LvglComponent::add_page(LvPageType *page) { this->pages_.push_back(page); page->setup(this->pages_.size() - 1); @@ -228,7 +232,8 @@ void LvglComponent::setup() { lv_log_register_print_cb(log_cb); #endif lv_init(); - lv_custom_event = static_cast(lv_event_register_id()); + lv_update_event = static_cast(lv_event_register_id()); + lv_api_event = static_cast(lv_event_register_id()); auto *display = this->displays_[0]; size_t buffer_pixels = display->get_width() * display->get_height() / this->buffer_frac_; auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8; diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 1497e1004a..3a3d1aa6c5 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -38,7 +38,8 @@ namespace esphome { namespace lvgl { -extern lv_event_code_t lv_custom_event; // NOLINT +extern lv_event_code_t lv_api_event; // NOLINT +extern lv_event_code_t lv_update_event; // NOLINT #ifdef USE_LVGL_COLOR inline lv_color_t lv_color_from(Color color) { return lv_color_make(color.red, color.green, color.blue); } #endif // USE_LVGL_COLOR @@ -133,6 +134,8 @@ class LvglComponent : public PollingComponent { void set_paused(bool paused, bool show_snow); void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event); void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2); + void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2, + lv_event_code_t event3); bool is_paused() const { return this->paused_; } void add_page(LvPageType *page); void show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time); diff --git a/esphome/components/lvgl/number/__init__.py b/esphome/components/lvgl/number/__init__.py index 53aef2790d..6336bb0632 100644 --- a/esphome/components/lvgl/number/__init__.py +++ b/esphome/components/lvgl/number/__init__.py @@ -3,9 +3,17 @@ from esphome.components import number import esphome.config_validation as cv from esphome.cpp_generator import MockObj -from ..defines import CONF_ANIMATED, CONF_LVGL_ID, CONF_WIDGET +from ..defines import CONF_ANIMATED, CONF_LVGL_ID, CONF_UPDATE_ON_RELEASE, CONF_WIDGET from ..lv_validation import animated -from ..lvcode import CUSTOM_EVENT, EVENT_ARG, LambdaContext, LvContext, lv, lv_add +from ..lvcode import ( + API_EVENT, + EVENT_ARG, + UPDATE_EVENT, + LambdaContext, + LvContext, + lv, + lv_add, +) from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvNumber, lvgl_ns from ..widgets import get_widgets @@ -19,6 +27,7 @@ CONFIG_SCHEMA = ( { cv.Required(CONF_WIDGET): cv.use_id(LvNumber), cv.Optional(CONF_ANIMATED, default=True): animated, + cv.Optional(CONF_UPDATE_ON_RELEASE, default=False): cv.boolean, } ) ) @@ -39,14 +48,19 @@ async def to_code(config): await widget.set_property( "value", MockObj("v") * MockObj(widget.get_scale()), config[CONF_ANIMATED] ) - lv.event_send(widget.obj, CUSTOM_EVENT, cg.nullptr) + lv.event_send(widget.obj, API_EVENT, cg.nullptr) async with LambdaContext(EVENT_ARG) as event: event.add(var.publish_state(widget.get_value())) + event_code = ( + LV_EVENT.VALUE_CHANGED + if not config[CONF_UPDATE_ON_RELEASE] + else LV_EVENT.RELEASED + ) async with LvContext(paren): lv_add(var.set_control_lambda(await control.get_lambda())) lv_add( paren.add_event_cb( - widget.obj, await event.get_lambda(), LV_EVENT.VALUE_CHANGED + widget.obj, await event.get_lambda(), UPDATE_EVENT, event_code ) ) lv_add(var.publish_state(widget.get_value())) diff --git a/esphome/components/lvgl/select/__init__.py b/esphome/components/lvgl/select/__init__.py index 34a70a23f7..b55bde13bc 100644 --- a/esphome/components/lvgl/select/__init__.py +++ b/esphome/components/lvgl/select/__init__.py @@ -4,7 +4,15 @@ import esphome.config_validation as cv from esphome.const import CONF_OPTIONS from ..defines import CONF_ANIMATED, CONF_LVGL_ID, CONF_WIDGET -from ..lvcode import CUSTOM_EVENT, EVENT_ARG, LambdaContext, LvContext, lv, lv_add +from ..lvcode import ( + API_EVENT, + EVENT_ARG, + UPDATE_EVENT, + LambdaContext, + LvContext, + lv, + lv_add, +) from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvSelect, lvgl_ns from ..widgets import get_widgets @@ -33,7 +41,7 @@ async def to_code(config): pub_ctx.add(selector.publish_index(widget.get_value())) async with LambdaContext([(cg.uint16, "v")]) as control: await widget.set_property("selected", "v", animated=config[CONF_ANIMATED]) - lv.event_send(widget.obj, CUSTOM_EVENT, cg.nullptr) + lv.event_send(widget.obj, API_EVENT, cg.nullptr) async with LvContext(paren) as ctx: lv_add(selector.set_control_lambda(await control.get_lambda())) ctx.add( @@ -41,6 +49,7 @@ async def to_code(config): widget.obj, await pub_ctx.get_lambda(), LV_EVENT.VALUE_CHANGED, + UPDATE_EVENT, ) ) lv_add(selector.publish_index(widget.get_value())) diff --git a/esphome/components/lvgl/sensor/__init__.py b/esphome/components/lvgl/sensor/__init__.py index 6e495eb685..82e21d5e95 100644 --- a/esphome/components/lvgl/sensor/__init__.py +++ b/esphome/components/lvgl/sensor/__init__.py @@ -3,7 +3,15 @@ from esphome.components.sensor import Sensor, new_sensor, sensor_schema import esphome.config_validation as cv from ..defines import CONF_LVGL_ID, CONF_WIDGET -from ..lvcode import EVENT_ARG, LVGL_COMP_ARG, LambdaContext, LvContext, lv_add +from ..lvcode import ( + API_EVENT, + EVENT_ARG, + LVGL_COMP_ARG, + UPDATE_EVENT, + LambdaContext, + LvContext, + lv_add, +) from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvNumber from ..widgets import Widget, get_widgets @@ -30,6 +38,10 @@ async def to_code(config): async with LvContext(paren, LVGL_COMP_ARG): lv_add( paren.add_event_cb( - widget.obj, await lamb.get_lambda(), LV_EVENT.VALUE_CHANGED + widget.obj, + await lamb.get_lambda(), + LV_EVENT.VALUE_CHANGED, + API_EVENT, + UPDATE_EVENT, ) ) diff --git a/esphome/components/lvgl/switch/__init__.py b/esphome/components/lvgl/switch/__init__.py index 831fa9308b..957fce17ff 100644 --- a/esphome/components/lvgl/switch/__init__.py +++ b/esphome/components/lvgl/switch/__init__.py @@ -5,8 +5,9 @@ from esphome.cpp_generator import MockObj from ..defines import CONF_LVGL_ID, CONF_WIDGET from ..lvcode import ( - CUSTOM_EVENT, + API_EVENT, EVENT_ARG, + UPDATE_EVENT, LambdaContext, LvConditional, LvContext, @@ -41,7 +42,7 @@ async def to_code(config): widget.add_state(LV_STATE.CHECKED) cond.else_() widget.clear_state(LV_STATE.CHECKED) - lv.event_send(widget.obj, CUSTOM_EVENT, cg.nullptr) + lv.event_send(widget.obj, API_EVENT, cg.nullptr) async with LvContext(paren) as ctx: lv_add(switch.set_control_lambda(await control.get_lambda())) ctx.add( @@ -49,6 +50,7 @@ async def to_code(config): widget.obj, await checked_ctx.get_lambda(), LV_EVENT.VALUE_CHANGED, + UPDATE_EVENT, ) ) lv_add(switch.publish_state(widget.get_value())) diff --git a/esphome/components/lvgl/text/__init__.py b/esphome/components/lvgl/text/__init__.py index 55f1b2b3fc..9ee494d8a0 100644 --- a/esphome/components/lvgl/text/__init__.py +++ b/esphome/components/lvgl/text/__init__.py @@ -4,7 +4,15 @@ from esphome.components.text import new_text import esphome.config_validation as cv from ..defines import CONF_LVGL_ID, CONF_WIDGET -from ..lvcode import CUSTOM_EVENT, EVENT_ARG, LambdaContext, LvContext, lv, lv_add +from ..lvcode import ( + API_EVENT, + EVENT_ARG, + UPDATE_EVENT, + LambdaContext, + LvContext, + lv, + lv_add, +) from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvText, lvgl_ns from ..widgets import get_widgets @@ -26,14 +34,17 @@ async def to_code(config): widget = widget[0] async with LambdaContext([(cg.std_string, "text_value")]) as control: await widget.set_property("text", "text_value.c_str())") - lv.event_send(widget.obj, CUSTOM_EVENT, None) + lv.event_send(widget.obj, API_EVENT, None) async with LambdaContext(EVENT_ARG) as lamb: lv_add(textvar.publish_state(widget.get_value())) async with LvContext(paren): widget.var.set_control_lambda(await control.get_lambda()) lv_add( paren.add_event_cb( - widget.obj, await lamb.get_lambda(), LV_EVENT.VALUE_CHANGED + widget.obj, + await lamb.get_lambda(), + LV_EVENT.VALUE_CHANGED, + UPDATE_EVENT, ) ) lv_add(textvar.publish_state(widget.get_value())) diff --git a/esphome/components/lvgl/text_sensor/__init__.py b/esphome/components/lvgl/text_sensor/__init__.py index c0f0bc36a8..cab715dce0 100644 --- a/esphome/components/lvgl/text_sensor/__init__.py +++ b/esphome/components/lvgl/text_sensor/__init__.py @@ -7,7 +7,7 @@ from esphome.components.text_sensor import ( import esphome.config_validation as cv from ..defines import CONF_LVGL_ID, CONF_WIDGET -from ..lvcode import EVENT_ARG, LambdaContext, LvContext +from ..lvcode import API_EVENT, EVENT_ARG, UPDATE_EVENT, LambdaContext, LvContext from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvText from ..widgets import get_widgets @@ -36,5 +36,7 @@ async def to_code(config): widget.obj, await pressed_ctx.get_lambda(), LV_EVENT.VALUE_CHANGED, + API_EVENT, + UPDATE_EVENT, ) ) diff --git a/esphome/components/lvgl/trigger.py b/esphome/components/lvgl/trigger.py index df87be718b..ba93aabb2d 100644 --- a/esphome/components/lvgl/trigger.py +++ b/esphome/components/lvgl/trigger.py @@ -11,7 +11,15 @@ from .defines import ( LV_EVENT_TRIGGERS, literal, ) -from .lvcode import EVENT_ARG, LambdaContext, LvConditional, lv, lv_add +from .lvcode import ( + API_EVENT, + EVENT_ARG, + UPDATE_EVENT, + LambdaContext, + LvConditional, + lv, + lv_add, +) from .types import LV_EVENT from .widgets import widget_map @@ -34,9 +42,16 @@ async def generate_triggers(lv_component): conf = conf[0] w.add_flag("LV_OBJ_FLAG_CLICKABLE") event = literal("LV_EVENT_" + LV_EVENT_MAP[event[3:].upper()]) - await add_trigger(conf, event, lv_component, w) + await add_trigger(conf, lv_component, w, event) for conf in w.config.get(CONF_ON_VALUE, ()): - await add_trigger(conf, LV_EVENT.VALUE_CHANGED, lv_component, w) + await add_trigger( + conf, + lv_component, + w, + LV_EVENT.VALUE_CHANGED, + API_EVENT, + UPDATE_EVENT, + ) # Generate align to directives while we're here if align_to := w.config.get(CONF_ALIGN_TO): @@ -47,7 +62,7 @@ async def generate_triggers(lv_component): lv.obj_align_to(w.obj, target, align, x, y) -async def add_trigger(conf, event, lv_component, w): +async def add_trigger(conf, lv_component, w, *events): tid = conf[CONF_TRIGGER_ID] trigger = cg.new_Pvariable(tid) args = w.get_args() @@ -56,4 +71,4 @@ async def add_trigger(conf, event, lv_component, w): async with LambdaContext(EVENT_ARG, where=tid) as context: with LvConditional(w.is_selected()): lv_add(trigger.trigger(value)) - lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), event)) + lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), *events)) diff --git a/tests/components/lvgl/common.yaml b/tests/components/lvgl/common.yaml index 35d924d939..002c7a118d 100644 --- a/tests/components/lvgl/common.yaml +++ b/tests/components/lvgl/common.yaml @@ -75,6 +75,7 @@ number: - platform: lvgl widget: slider_id name: LVGL Slider + update_on_release: true - platform: lvgl widget: lv_arc id: lvgl_arc_number From bec2d42c793c59b18e15a0fbb5cbe22761858290 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:06:13 +1000 Subject: [PATCH 04/12] Add `color_filter_opa` style property (#7276) --- esphome/components/lvgl/schemas.py | 1 + tests/components/lvgl/lvgl-package.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index e4b1c3f8fa..f1c7ff4df6 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -103,6 +103,7 @@ STYLE_PROPS = { ).several_of, "border_width": cv.positive_int, "clip_corner": lvalid.lv_bool, + "color_filter_opa": lvalid.opacity, "height": lvalid.size, "image_recolor": lvalid.lv_color, "image_recolor_opa": lvalid.opacity, diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 09ff9c9d39..54022354f5 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -101,6 +101,7 @@ lvgl: border_side: [bottom, left] border_width: 4 clip_corner: false + color_filter_opa: transp height: 50% image_recolor: light_blue image_recolor_opa: cover From 56e05998efb14b7e91fea7e4ef3cfe76a7128c61 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Wed, 14 Aug 2024 04:08:10 +0200 Subject: [PATCH 05/12] [code-quality] fix clang-tidy wake_on_lan (#7275) --- esphome/components/wake_on_lan/wake_on_lan.cpp | 2 ++ esphome/components/wake_on_lan/wake_on_lan.h | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp index 080e1bbac8..d18cdf89c8 100644 --- a/esphome/components/wake_on_lan/wake_on_lan.cpp +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -1,4 +1,5 @@ #include "wake_on_lan.h" +#ifdef USE_NETWORK #include "esphome/core/log.h" #include "esphome/components/network/ip_address.h" #include "esphome/components/network/util.h" @@ -85,3 +86,4 @@ void WakeOnLanButton::setup() { } // namespace wake_on_lan } // namespace esphome +#endif diff --git a/esphome/components/wake_on_lan/wake_on_lan.h b/esphome/components/wake_on_lan/wake_on_lan.h index 42cb3a9268..f516c4d669 100644 --- a/esphome/components/wake_on_lan/wake_on_lan.h +++ b/esphome/components/wake_on_lan/wake_on_lan.h @@ -1,5 +1,6 @@ #pragma once - +#include "esphome/core/defines.h" +#ifdef USE_NETWORK #include "esphome/components/button/button.h" #include "esphome/core/component.h" #if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) @@ -32,3 +33,4 @@ class WakeOnLanButton : public button::Button, public Component { } // namespace wake_on_lan } // namespace esphome +#endif From 4cb174585c0521801d6e8ffda189d37b5a40aa30 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Wed, 14 Aug 2024 04:14:29 +0200 Subject: [PATCH 06/12] [code-quality] fix readability-braces-around-statements (#7273) --- .../binary/light/binary_light_output.h | 5 +- esphome/components/demo/demo_sensor.h | 5 +- .../light/addressable_light_effect.h | 20 +++--- esphome/components/lvgl/number/lvgl_number.h | 5 +- esphome/components/lvgl/switch/lvgl_switch.h | 5 +- esphome/components/lvgl/text/lvgl_text.h | 5 +- esphome/components/rgbct/rgbct_light_output.h | 5 +- esphome/components/rgbw/rgbw_light_output.h | 5 +- esphome/components/rgbww/rgbww_light_output.h | 5 +- .../components/spi_led_strip/spi_led_strip.h | 5 +- esphome/core/application.h | 63 ++++++++++++------- esphome/core/base_automation.h | 5 +- esphome/core/color.h | 40 +++++++----- 13 files changed, 108 insertions(+), 65 deletions(-) diff --git a/esphome/components/binary/light/binary_light_output.h b/esphome/components/binary/light/binary_light_output.h index 86c83aff5c..8346a82cf0 100644 --- a/esphome/components/binary/light/binary_light_output.h +++ b/esphome/components/binary/light/binary_light_output.h @@ -18,10 +18,11 @@ class BinaryLightOutput : public light::LightOutput { void write_state(light::LightState *state) override { bool binary; state->current_values_as_binary(&binary); - if (binary) + if (binary) { this->output_->turn_on(); - else + } else { this->output_->turn_off(); + } } protected: diff --git a/esphome/components/demo/demo_sensor.h b/esphome/components/demo/demo_sensor.h index b4afa03e11..d965d987de 100644 --- a/esphome/components/demo/demo_sensor.h +++ b/esphome/components/demo/demo_sensor.h @@ -16,10 +16,11 @@ class DemoSensor : public sensor::Sensor, public PollingComponent { float base = std::isnan(this->state) ? 0.0f : this->state; this->publish_state(base + val * 10); } else { - if (val < 0.1) + if (val < 0.1) { this->publish_state(NAN); - else + } else { this->publish_state(val * 100); + } } } }; diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 73083a58b7..d622ec0375 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -114,10 +114,11 @@ class AddressableColorWipeEffect : public AddressableLightEffect { if (now - this->last_add_ < this->add_led_interval_) return; this->last_add_ = now; - if (this->reverse_) + if (this->reverse_) { it.shift_left(1); - else + } else { it.shift_right(1); + } const AddressableColorWipeEffectColor &color = this->colors_[this->at_color_]; Color esp_color = Color(color.r, color.g, color.b, color.w); if (color.gradient) { @@ -127,10 +128,11 @@ class AddressableColorWipeEffect : public AddressableLightEffect { uint8_t gradient = 255 * ((float) this->leds_added_ / color.num_leds); esp_color = esp_color.gradient(next_esp_color, gradient); } - if (this->reverse_) + if (this->reverse_) { it[-1] = esp_color; - else + } else { it[0] = esp_color; + } if (++this->leds_added_ >= color.num_leds) { this->leds_added_ = 0; this->at_color_ = (this->at_color_ + 1) % this->colors_.size(); @@ -207,10 +209,11 @@ class AddressableTwinkleEffect : public AddressableLightEffect { const uint8_t sine = half_sin8(view.get_effect_data()); view = current_color * sine; const uint8_t new_pos = view.get_effect_data() + pos_add; - if (new_pos < view.get_effect_data()) + if (new_pos < view.get_effect_data()) { view.set_effect_data(0); - else + } else { view.set_effect_data(new_pos); + } } else { view = Color::BLACK; } @@ -254,10 +257,11 @@ class AddressableRandomTwinkleEffect : public AddressableLightEffect { view = Color(((color >> 2) & 1) * sine, ((color >> 1) & 1) * sine, ((color >> 0) & 1) * sine); } const uint8_t new_x = x + pos_add; - if (new_x > 0b11111) + if (new_x > 0b11111) { view.set_effect_data(0); - else + } else { view.set_effect_data((new_x << 3) | color); + } } else { view = Color(0, 0, 0, 0); } diff --git a/esphome/components/lvgl/number/lvgl_number.h b/esphome/components/lvgl/number/lvgl_number.h index 659fc615c9..a70c9eab9c 100644 --- a/esphome/components/lvgl/number/lvgl_number.h +++ b/esphome/components/lvgl/number/lvgl_number.h @@ -20,10 +20,11 @@ class LVGLNumber : public number::Number { protected: void control(float value) override { - if (this->control_lambda_ != nullptr) + if (this->control_lambda_ != nullptr) { this->control_lambda_(value); - else + } else { this->initial_state_ = value; + } } std::function control_lambda_{}; optional initial_state_{}; diff --git a/esphome/components/lvgl/switch/lvgl_switch.h b/esphome/components/lvgl/switch/lvgl_switch.h index 67be11faba..dbc885219b 100644 --- a/esphome/components/lvgl/switch/lvgl_switch.h +++ b/esphome/components/lvgl/switch/lvgl_switch.h @@ -20,10 +20,11 @@ class LVGLSwitch : public switch_::Switch { protected: void write_state(bool value) override { - if (this->state_lambda_ != nullptr) + if (this->state_lambda_ != nullptr) { this->state_lambda_(value); - else + } else { this->initial_state_ = value; + } } std::function state_lambda_{}; optional initial_state_{}; diff --git a/esphome/components/lvgl/text/lvgl_text.h b/esphome/components/lvgl/text/lvgl_text.h index 4bd5b76744..d3513c3697 100644 --- a/esphome/components/lvgl/text/lvgl_text.h +++ b/esphome/components/lvgl/text/lvgl_text.h @@ -20,10 +20,11 @@ class LVGLText : public text::Text { protected: void control(const std::string &value) override { - if (this->control_lambda_ != nullptr) + if (this->control_lambda_ != nullptr) { this->control_lambda_(value); - else + } else { this->initial_state_ = value; + } } std::function control_lambda_{}; optional initial_state_{}; diff --git a/esphome/components/rgbct/rgbct_light_output.h b/esphome/components/rgbct/rgbct_light_output.h index 9257d67cd1..9e23f783ae 100644 --- a/esphome/components/rgbct/rgbct_light_output.h +++ b/esphome/components/rgbct/rgbct_light_output.h @@ -23,10 +23,11 @@ class RGBCTLightOutput : public light::LightOutput { light::LightTraits get_traits() override { auto traits = light::LightTraits(); - if (this->color_interlock_) + if (this->color_interlock_) { traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE}); - else + } else { traits.set_supported_color_modes({light::ColorMode::RGB_COLOR_TEMPERATURE, light::ColorMode::COLOR_TEMPERATURE}); + } traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); return traits; diff --git a/esphome/components/rgbw/rgbw_light_output.h b/esphome/components/rgbw/rgbw_light_output.h index 0f55775608..a2ab17b75d 100644 --- a/esphome/components/rgbw/rgbw_light_output.h +++ b/esphome/components/rgbw/rgbw_light_output.h @@ -16,10 +16,11 @@ class RGBWLightOutput : public light::LightOutput { void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); - if (this->color_interlock_) + if (this->color_interlock_) { traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE}); - else + } else { traits.set_supported_color_modes({light::ColorMode::RGB_WHITE}); + } return traits; } void write_state(light::LightState *state) override { diff --git a/esphome/components/rgbww/rgbww_light_output.h b/esphome/components/rgbww/rgbww_light_output.h index 5a86b88595..9687360059 100644 --- a/esphome/components/rgbww/rgbww_light_output.h +++ b/esphome/components/rgbww/rgbww_light_output.h @@ -20,10 +20,11 @@ class RGBWWLightOutput : public light::LightOutput { void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); - if (this->color_interlock_) + if (this->color_interlock_) { traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLD_WARM_WHITE}); - else + } else { traits.set_supported_color_modes({light::ColorMode::RGB_COLD_WARM_WHITE}); + } traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); return traits; diff --git a/esphome/components/spi_led_strip/spi_led_strip.h b/esphome/components/spi_led_strip/spi_led_strip.h index 8b713378ec..1b317cdd69 100644 --- a/esphome/components/spi_led_strip/spi_led_strip.h +++ b/esphome/components/spi_led_strip/spi_led_strip.h @@ -46,10 +46,11 @@ class SpiLedStrip : public light::AddressableLight, void dump_config() override { esph_log_config(TAG, "SPI LED Strip:"); esph_log_config(TAG, " LEDs: %d", this->num_leds_); - if (this->data_rate_ >= spi::DATA_RATE_1MHZ) + if (this->data_rate_ >= spi::DATA_RATE_1MHZ) { esph_log_config(TAG, " Data rate: %uMHz", (unsigned) (this->data_rate_ / 1000000)); - else + } else { esph_log_config(TAG, " Data rate: %ukHz", (unsigned) (this->data_rate_ / 1000)); + } } void write_state(light::LightState *state) override { diff --git a/esphome/core/application.h b/esphome/core/application.h index 2697357456..462beb1f25 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -246,162 +246,180 @@ class Application { #ifdef USE_BINARY_SENSOR const std::vector &get_binary_sensors() { return this->binary_sensors_; } binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->binary_sensors_) + for (auto *obj : this->binary_sensors_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_SWITCH const std::vector &get_switches() { return this->switches_; } switch_::Switch *get_switch_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->switches_) + for (auto *obj : this->switches_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_BUTTON const std::vector &get_buttons() { return this->buttons_; } button::Button *get_button_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->buttons_) + for (auto *obj : this->buttons_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_SENSOR const std::vector &get_sensors() { return this->sensors_; } sensor::Sensor *get_sensor_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->sensors_) + for (auto *obj : this->sensors_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_TEXT_SENSOR const std::vector &get_text_sensors() { return this->text_sensors_; } text_sensor::TextSensor *get_text_sensor_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->text_sensors_) + for (auto *obj : this->text_sensors_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_FAN const std::vector &get_fans() { return this->fans_; } fan::Fan *get_fan_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->fans_) + for (auto *obj : this->fans_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_COVER const std::vector &get_covers() { return this->covers_; } cover::Cover *get_cover_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->covers_) + for (auto *obj : this->covers_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_LIGHT const std::vector &get_lights() { return this->lights_; } light::LightState *get_light_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->lights_) + for (auto *obj : this->lights_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_CLIMATE const std::vector &get_climates() { return this->climates_; } climate::Climate *get_climate_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->climates_) + for (auto *obj : this->climates_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_NUMBER const std::vector &get_numbers() { return this->numbers_; } number::Number *get_number_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->numbers_) + for (auto *obj : this->numbers_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_DATETIME_DATE const std::vector &get_dates() { return this->dates_; } datetime::DateEntity *get_date_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->dates_) + for (auto *obj : this->dates_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_DATETIME_TIME const std::vector &get_times() { return this->times_; } datetime::TimeEntity *get_time_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->times_) + for (auto *obj : this->times_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_DATETIME_DATETIME const std::vector &get_datetimes() { return this->datetimes_; } datetime::DateTimeEntity *get_datetime_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->datetimes_) + for (auto *obj : this->datetimes_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_TEXT const std::vector &get_texts() { return this->texts_; } text::Text *get_text_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->texts_) + for (auto *obj : this->texts_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_SELECT const std::vector &get_selects() { return this->selects_; } select::Select *get_select_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->selects_) + for (auto *obj : this->selects_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_LOCK const std::vector &get_locks() { return this->locks_; } lock::Lock *get_lock_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->locks_) + for (auto *obj : this->locks_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_VALVE const std::vector &get_valves() { return this->valves_; } valve::Valve *get_valve_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->valves_) + for (auto *obj : this->valves_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif #ifdef USE_MEDIA_PLAYER const std::vector &get_media_players() { return this->media_players_; } media_player::MediaPlayer *get_media_player_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->media_players_) + for (auto *obj : this->media_players_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif @@ -411,9 +429,10 @@ class Application { return this->alarm_control_panels_; } alarm_control_panel::AlarmControlPanel *get_alarm_control_panel_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->alarm_control_panels_) + for (auto *obj : this->alarm_control_panels_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif @@ -421,9 +440,10 @@ class Application { #ifdef USE_EVENT const std::vector &get_events() { return this->events_; } event::Event *get_event_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->events_) + for (auto *obj : this->events_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif @@ -431,9 +451,10 @@ class Application { #ifdef USE_UPDATE const std::vector &get_updates() { return this->updates_; } update::UpdateEntity *get_update_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->updates_) + for (auto *obj : this->updates_) { if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; + } return nullptr; } #endif diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 1bf0efb9a4..dcf7da2f21 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -278,10 +278,11 @@ template class RepeatAction : public Action { this->then_.add_actions(actions); this->then_.add_action(new LambdaAction([this](uint32_t iteration, Ts... x) { iteration++; - if (iteration >= this->count_.value(x...)) + if (iteration >= this->count_.value(x...)) { this->play_next_tuple_(this->var_); - else + } else { this->then_.play(iteration, x...); + } })); } diff --git a/esphome/core/color.h b/esphome/core/color.h index 8965d9fc83..1c43fd9d3e 100644 --- a/esphome/core/color.h +++ b/esphome/core/color.h @@ -85,22 +85,26 @@ struct Color { } inline Color operator+(const Color &add) const ESPHOME_ALWAYS_INLINE { Color ret; - if (uint8_t(add.r + this->r) < this->r) + if (uint8_t(add.r + this->r) < this->r) { ret.r = 255; - else + } else { ret.r = this->r + add.r; - if (uint8_t(add.g + this->g) < this->g) + } + if (uint8_t(add.g + this->g) < this->g) { ret.g = 255; - else + } else { ret.g = this->g + add.g; - if (uint8_t(add.b + this->b) < this->b) + } + if (uint8_t(add.b + this->b) < this->b) { ret.b = 255; - else + } else { ret.b = this->b + add.b; - if (uint8_t(add.w + this->w) < this->w) + } + if (uint8_t(add.w + this->w) < this->w) { ret.w = 255; - else + } else { ret.w = this->w + add.w; + } return ret; } inline Color &operator+=(const Color &add) ESPHOME_ALWAYS_INLINE { return *this = (*this) + add; } @@ -108,22 +112,26 @@ struct Color { inline Color &operator+=(uint8_t add) ESPHOME_ALWAYS_INLINE { return *this = (*this) + add; } inline Color operator-(const Color &subtract) const ESPHOME_ALWAYS_INLINE { Color ret; - if (subtract.r > this->r) + if (subtract.r > this->r) { ret.r = 0; - else + } else { ret.r = this->r - subtract.r; - if (subtract.g > this->g) + } + if (subtract.g > this->g) { ret.g = 0; - else + } else { ret.g = this->g - subtract.g; - if (subtract.b > this->b) + } + if (subtract.b > this->b) { ret.b = 0; - else + } else { ret.b = this->b - subtract.b; - if (subtract.w > this->w) + } + if (subtract.w > this->w) { ret.w = 0; - else + } else { ret.w = this->w - subtract.w; + } return ret; } inline Color &operator-=(const Color &subtract) ESPHOME_ALWAYS_INLINE { return *this = (*this) - subtract; } From 8756b41b6344a61a49a3b4cdb66437b1b6064036 Mon Sep 17 00:00:00 2001 From: Olivier ARCHER Date: Wed, 14 Aug 2024 04:19:46 +0200 Subject: [PATCH 07/12] [mqtt] fix missing initializer in MQTTClientComponent::disable_discovery (#7271) --- esphome/components/mqtt/mqtt_client.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 876367aaea..c19b24c0cf 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -632,6 +632,7 @@ void MQTTClientComponent::disable_discovery() { this->discovery_info_ = MQTTDiscoveryInfo{ .prefix = "", .retain = false, + .discover_ip = false, .clean = false, .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR, .object_id_generator = MQTT_NONE_OBJECT_ID_GENERATOR, From b2b23f2a4f2fbbba0ded4b77104df871a2f27260 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Wed, 14 Aug 2024 04:21:19 +0200 Subject: [PATCH 08/12] [code-quality] fix readability-named-parameter (#7272) --- esphome/core/automation.h | 8 +++++--- esphome/core/optional.h | 30 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 5a0a17ea1a..e77e453431 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -82,7 +82,7 @@ template class Condition { } protected: - template bool check_tuple_(const std::tuple &tuple, seq) { + template bool check_tuple_(const std::tuple &tuple, seq /*unused*/) { return this->check(std::get(tuple)...); } }; @@ -156,7 +156,7 @@ template class Action { } } } - template void play_next_tuple_(const std::tuple &tuple, seq) { + template void play_next_tuple_(const std::tuple &tuple, seq /*unused*/) { this->play_next_(std::get(tuple)...); } void play_next_tuple_(const std::tuple &tuple) { @@ -223,7 +223,9 @@ template class ActionList { } protected: - template void play_tuple_(const std::tuple &tuple, seq) { this->play(std::get(tuple)...); } + template void play_tuple_(const std::tuple &tuple, seq /*unused*/) { + this->play(std::get(tuple)...); + } Action *actions_begin_{nullptr}; Action *actions_end_{nullptr}; diff --git a/esphome/core/optional.h b/esphome/core/optional.h index 770b77081e..1e28ef1354 100644 --- a/esphome/core/optional.h +++ b/esphome/core/optional.h @@ -24,7 +24,7 @@ namespace esphome { struct nullopt_t { // NOLINT struct init {}; // NOLINT - nullopt_t(init) {} + nullopt_t(init /*unused*/) {} }; // extra parenthesis to prevent the most vexing parse: @@ -42,13 +42,13 @@ template class optional { // NOLINT optional() {} - optional(nullopt_t) {} + optional(nullopt_t /*unused*/) {} optional(T const &arg) : has_value_(true), value_(arg) {} // NOLINT template optional(optional const &other) : has_value_(other.has_value()), value_(other.value()) {} - optional &operator=(nullopt_t) { + optional &operator=(nullopt_t /*unused*/) { reset(); return *this; } @@ -130,29 +130,29 @@ template inline bool operator>=(optional const &x, op // Comparison with nullopt -template inline bool operator==(optional const &x, nullopt_t) { return (!x); } +template inline bool operator==(optional const &x, nullopt_t /*unused*/) { return (!x); } -template inline bool operator==(nullopt_t, optional const &x) { return (!x); } +template inline bool operator==(nullopt_t /*unused*/, optional const &x) { return (!x); } -template inline bool operator!=(optional const &x, nullopt_t) { return bool(x); } +template inline bool operator!=(optional const &x, nullopt_t /*unused*/) { return bool(x); } -template inline bool operator!=(nullopt_t, optional const &x) { return bool(x); } +template inline bool operator!=(nullopt_t /*unused*/, optional const &x) { return bool(x); } -template inline bool operator<(optional const &, nullopt_t) { return false; } +template inline bool operator<(optional const & /*unused*/, nullopt_t /*unused*/) { return false; } -template inline bool operator<(nullopt_t, optional const &x) { return bool(x); } +template inline bool operator<(nullopt_t /*unused*/, optional const &x) { return bool(x); } -template inline bool operator<=(optional const &x, nullopt_t) { return (!x); } +template inline bool operator<=(optional const &x, nullopt_t /*unused*/) { return (!x); } -template inline bool operator<=(nullopt_t, optional const &) { return true; } +template inline bool operator<=(nullopt_t /*unused*/, optional const & /*unused*/) { return true; } -template inline bool operator>(optional const &x, nullopt_t) { return bool(x); } +template inline bool operator>(optional const &x, nullopt_t /*unused*/) { return bool(x); } -template inline bool operator>(nullopt_t, optional const &) { return false; } +template inline bool operator>(nullopt_t /*unused*/, optional const & /*unused*/) { return false; } -template inline bool operator>=(optional const &, nullopt_t) { return true; } +template inline bool operator>=(optional const & /*unused*/, nullopt_t /*unused*/) { return true; } -template inline bool operator>=(nullopt_t, optional const &x) { return (!x); } +template inline bool operator>=(nullopt_t /*unused*/, optional const &x) { return (!x); } // Comparison with T From 8f093823672a49d63d9276c4072d2a3189d194d6 Mon Sep 17 00:00:00 2001 From: Philippe Wechsler <29612400+MadMonkey87@users.noreply.github.com> Date: Wed, 14 Aug 2024 04:25:45 +0200 Subject: [PATCH 09/12] support illuminance for airthings wave plus device (#5203) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../airthings_wave_plus/airthings_wave_plus.cpp | 7 +++++-- .../airthings_wave_plus/airthings_wave_plus.h | 2 ++ esphome/components/airthings_wave_plus/sensor.py | 12 ++++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index a32128e992..8c8c514fdb 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -14,8 +14,6 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) { ESP_LOGD(TAG, "version = %d", value->version); if (value->version == 1) { - ESP_LOGD(TAG, "ambient light = %d", value->ambientLight); - if (this->humidity_sensor_ != nullptr) { this->humidity_sensor_->publish_state(value->humidity / 2.0f); } @@ -43,6 +41,10 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) { if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { this->tvoc_sensor_->publish_state(value->voc); } + + if (this->illuminance_sensor_ != nullptr) { + this->illuminance_sensor_->publish_state(value->ambientLight); + } } else { ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); } @@ -68,6 +70,7 @@ void AirthingsWavePlus::dump_config() { LOG_SENSOR(" ", "Radon", this->radon_sensor_); LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); LOG_SENSOR(" ", "CO2", this->co2_sensor_); + LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_); } AirthingsWavePlus::AirthingsWavePlus() { diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h index 23c8cbb166..bd7a40ef8b 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.h +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -22,6 +22,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; } + void set_illuminance(sensor::Sensor *illuminance) { illuminance_sensor_ = illuminance; } protected: bool is_valid_radon_value_(uint16_t radon); @@ -32,6 +33,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { sensor::Sensor *radon_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr}; sensor::Sensor *co2_sensor_{nullptr}; + sensor::Sensor *illuminance_sensor_{nullptr}; struct WavePlusReadings { uint8_t version; diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py index 643a2bfb68..d28c7e2abc 100644 --- a/esphome/components/airthings_wave_plus/sensor.py +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -12,6 +12,9 @@ from esphome.const import ( CONF_CO2, UNIT_BECQUEREL_PER_CUBIC_METER, UNIT_PARTS_PER_MILLION, + CONF_ILLUMINANCE, + UNIT_LUX, + DEVICE_CLASS_ILLUMINANCE, ) DEPENDENCIES = airthings_wave_base.DEPENDENCIES @@ -45,6 +48,12 @@ CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend( device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), } ) @@ -62,3 +71,6 @@ async def to_code(config): if config_co2 := config.get(CONF_CO2): sens = await sensor.new_sensor(config_co2) cg.add(var.set_co2(sens)) + if config_illuminance := config.get(CONF_ILLUMINANCE): + sens = await sensor.new_sensor(config_illuminance) + cg.add(var.set_illuminance(sens)) From d6f130e35ab2ecd0d2c09ef31459c205852aa751 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Tue, 13 Aug 2024 23:40:07 -0400 Subject: [PATCH 10/12] [micro_wake_word] Bump ESPMicroSpeechFeatures version to 1.1.0 (#7249) --- .../components/micro_wake_word/__init__.py | 47 +++++++++---------- platformio.ini | 2 +- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py index c2faca25f4..cd45f75b01 100644 --- a/esphome/components/micro_wake_word/__init__.py +++ b/esphome/components/micro_wake_word/__init__.py @@ -1,39 +1,34 @@ -import logging - -import json import hashlib -from urllib.parse import urljoin +import json +import logging from pathlib import Path +from urllib.parse import urljoin + import requests -import esphome.config_validation as cv -import esphome.codegen as cg - -from esphome.core import CORE, HexInt - -from esphome.components import esp32, microphone -from esphome import automation, git, external_files +from esphome import automation, external_files, git from esphome.automation import register_action, register_condition - - +import esphome.codegen as cg +from esphome.components import esp32, microphone +import esphome.config_validation as cv from esphome.const import ( - __version__, + CONF_FILE, CONF_ID, CONF_MICROPHONE, CONF_MODEL, - CONF_URL, - CONF_FILE, + CONF_PASSWORD, CONF_PATH, + CONF_RAW_DATA_ID, CONF_REF, CONF_REFRESH, CONF_TYPE, + CONF_URL, CONF_USERNAME, - CONF_PASSWORD, - CONF_RAW_DATA_ID, TYPE_GIT, TYPE_LOCAL, + __version__, ) - +from esphome.core import CORE, HexInt _LOGGER = logging.getLogger(__name__) @@ -174,12 +169,12 @@ def _convert_manifest_v1_to_v2(v1_manifest): CONF_SLIDING_WINDOW_AVERAGE_SIZE ] del v2_manifest[KEY_MICRO][CONF_SLIDING_WINDOW_AVERAGE_SIZE] - v2_manifest[KEY_MICRO][ - CONF_TENSOR_ARENA_SIZE - ] = 45672 # Original Inception-based V1 manifest models require a minimum of 45672 bytes - v2_manifest[KEY_MICRO][ - CONF_FEATURE_STEP_SIZE - ] = 20 # Original Inception-based V1 manifest models use a 20 ms feature step size + + # Original Inception-based V1 manifest models require a minimum of 45672 bytes + v2_manifest[KEY_MICRO][CONF_TENSOR_ARENA_SIZE] = 45672 + + # Original Inception-based V1 manifest models use a 20 ms feature step size + v2_manifest[KEY_MICRO][CONF_FEATURE_STEP_SIZE] = 20 return v2_manifest @@ -502,7 +497,7 @@ async def to_code(config): ) cg.add(var.set_features_step_size(manifest[KEY_MICRO][CONF_FEATURE_STEP_SIZE])) - cg.add_library("kahrendt/ESPMicroSpeechFeatures", "1.0.0") + cg.add_library("kahrendt/ESPMicroSpeechFeatures", "1.1.0") MICRO_WAKE_WORD_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(MicroWakeWord)}) diff --git a/platformio.ini b/platformio.ini index 87a239207f..4a0a3f2ef4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -145,7 +145,7 @@ framework = espidf lib_deps = ${common:idf.lib_deps} droscy/esp_wireguard@0.4.2 ; wireguard - kahrendt/ESPMicroSpeechFeatures@1.0.0 ; micro_wake_word + kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word build_flags = ${common:idf.build_flags} -Wno-nonnull-compare From cf6ea7cb2cf1636cb18e60ecf010fa6bb559c464 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Wed, 14 Aug 2024 05:42:43 +0200 Subject: [PATCH 11/12] Implement the finish() method and action. implement the is_stopped condition (#7255) --- .../i2s_audio/speaker/i2s_audio_speaker.cpp | 12 ++++++++++-- .../i2s_audio/speaker/i2s_audio_speaker.h | 2 ++ esphome/components/speaker/__init__.py | 19 ++++++++++++++----- esphome/components/speaker/automation.h | 10 ++++++++++ esphome/components/speaker/speaker.h | 5 +++++ tests/components/speaker/test.esp32-ard.yaml | 11 +++++++++-- .../components/speaker/test.esp32-c3-ard.yaml | 11 +++++++++-- .../components/speaker/test.esp32-c3-idf.yaml | 11 +++++++++-- tests/components/speaker/test.esp32-idf.yaml | 11 +++++++++-- 9 files changed, 77 insertions(+), 15 deletions(-) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 1c6c50d8c9..cf5a2c2766 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -180,7 +180,11 @@ void I2SAudioSpeaker::player_task(void *params) { } } -void I2SAudioSpeaker::stop() { +void I2SAudioSpeaker::stop() { this->stop_(false); } + +void I2SAudioSpeaker::finish() { this->stop_(true); } + +void I2SAudioSpeaker::stop_(bool wait_on_empty) { if (this->is_failed()) return; if (this->state_ == speaker::STATE_STOPPED) @@ -192,7 +196,11 @@ void I2SAudioSpeaker::stop() { this->state_ = speaker::STATE_STOPPING; DataEvent data; data.stop = true; - xQueueSendToFront(this->buffer_queue_, &data, portMAX_DELAY); + if (wait_on_empty) { + xQueueSend(this->buffer_queue_, &data, portMAX_DELAY); + } else { + xQueueSendToFront(this->buffer_queue_, &data, portMAX_DELAY); + } } void I2SAudioSpeaker::watch_() { diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index 1800feaeec..0bdb67ceba 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -53,6 +53,7 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud void start() override; void stop() override; + void finish() override; size_t play(const uint8_t *data, size_t length) override; @@ -60,6 +61,7 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud protected: void start_(); + void stop_(bool wait_on_empty); void watch_(); static void player_task(void *params); diff --git a/esphome/components/speaker/__init__.py b/esphome/components/speaker/__init__.py index 79d5df8c5a..d28b726d1f 100644 --- a/esphome/components/speaker/__init__.py +++ b/esphome/components/speaker/__init__.py @@ -1,13 +1,11 @@ from esphome import automation -import esphome.config_validation as cv -import esphome.codegen as cg - from esphome.automation import maybe_simple_id -from esphome.const import CONF_ID, CONF_DATA +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_DATA, CONF_ID from esphome.core import CORE from esphome.coroutine import coroutine_with_priority - CODEOWNERS = ["@jesserockz"] IS_PLATFORM_COMPONENT = True @@ -22,8 +20,12 @@ PlayAction = speaker_ns.class_( StopAction = speaker_ns.class_( "StopAction", automation.Action, cg.Parented.template(Speaker) ) +FinishAction = speaker_ns.class_( + "FinishAction", automation.Action, cg.Parented.template(Speaker) +) IsPlayingCondition = speaker_ns.class_("IsPlayingCondition", automation.Condition) +IsStoppedCondition = speaker_ns.class_("IsStoppedCondition", automation.Condition) async def setup_speaker_core_(var, config): @@ -75,11 +77,18 @@ async def speaker_play_action(config, action_id, template_arg, args): automation.register_action("speaker.stop", StopAction, SPEAKER_AUTOMATION_SCHEMA)( speaker_action ) +automation.register_action("speaker.finish", FinishAction, SPEAKER_AUTOMATION_SCHEMA)( + speaker_action +) automation.register_condition( "speaker.is_playing", IsPlayingCondition, SPEAKER_AUTOMATION_SCHEMA )(speaker_action) +automation.register_condition( + "speaker.is_stopped", IsStoppedCondition, SPEAKER_AUTOMATION_SCHEMA +)(speaker_action) + @coroutine_with_priority(100.0) async def to_code(config): diff --git a/esphome/components/speaker/automation.h b/esphome/components/speaker/automation.h index e28991a0d1..2716fe6100 100644 --- a/esphome/components/speaker/automation.h +++ b/esphome/components/speaker/automation.h @@ -39,10 +39,20 @@ template class StopAction : public Action, public Parente void play(Ts... x) override { this->parent_->stop(); } }; +template class FinishAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->finish(); } +}; + template class IsPlayingCondition : public Condition, public Parented { public: bool check(Ts... x) override { return this->parent_->is_running(); } }; +template class IsStoppedCondition : public Condition, public Parented { + public: + bool check(Ts... x) override { return this->parent_->is_stopped(); } +}; + } // namespace speaker } // namespace esphome diff --git a/esphome/components/speaker/speaker.h b/esphome/components/speaker/speaker.h index b494873160..142231881c 100644 --- a/esphome/components/speaker/speaker.h +++ b/esphome/components/speaker/speaker.h @@ -17,10 +17,15 @@ class Speaker { virtual void start() = 0; virtual void stop() = 0; + // In compare between *STOP()* and *FINISH()*; *FINISH()* will stop after emptying the play buffer, + // while *STOP()* will break directly. + // When finish() is not implemented on the plateform component it should just do a normal stop. + virtual void finish() { this->stop(); } virtual bool has_buffered_data() const = 0; bool is_running() const { return this->state_ == STATE_RUNNING; } + bool is_stopped() const { return this->state_ == STATE_STOPPED; } protected: State state_{STATE_STOPPED}; diff --git a/tests/components/speaker/test.esp32-ard.yaml b/tests/components/speaker/test.esp32-ard.yaml index 416e203d7b..e10c3e88c1 100644 --- a/tests/components/speaker/test.esp32-ard.yaml +++ b/tests/components/speaker/test.esp32-ard.yaml @@ -1,8 +1,15 @@ esphome: on_boot: then: - - speaker.play: [0, 1, 2, 3] - - speaker.stop + - if: + condition: speaker.is_stopped + then: + - speaker.play: [0, 1, 2, 3] + - if: + condition: speaker.is_playing + then: + - speaker.finish: + - speaker.stop: i2s_audio: i2s_lrclk_pin: 16 diff --git a/tests/components/speaker/test.esp32-c3-ard.yaml b/tests/components/speaker/test.esp32-c3-ard.yaml index c7809baace..08699d8b22 100644 --- a/tests/components/speaker/test.esp32-c3-ard.yaml +++ b/tests/components/speaker/test.esp32-c3-ard.yaml @@ -1,8 +1,15 @@ esphome: on_boot: then: - - speaker.play: [0, 1, 2, 3] - - speaker.stop + - if: + condition: speaker.is_stopped + then: + - speaker.play: [0, 1, 2, 3] + - if: + condition: speaker.is_playing + then: + - speaker.finish: + - speaker.stop: i2s_audio: i2s_lrclk_pin: 6 diff --git a/tests/components/speaker/test.esp32-c3-idf.yaml b/tests/components/speaker/test.esp32-c3-idf.yaml index c7809baace..08699d8b22 100644 --- a/tests/components/speaker/test.esp32-c3-idf.yaml +++ b/tests/components/speaker/test.esp32-c3-idf.yaml @@ -1,8 +1,15 @@ esphome: on_boot: then: - - speaker.play: [0, 1, 2, 3] - - speaker.stop + - if: + condition: speaker.is_stopped + then: + - speaker.play: [0, 1, 2, 3] + - if: + condition: speaker.is_playing + then: + - speaker.finish: + - speaker.stop: i2s_audio: i2s_lrclk_pin: 6 diff --git a/tests/components/speaker/test.esp32-idf.yaml b/tests/components/speaker/test.esp32-idf.yaml index 416e203d7b..e10c3e88c1 100644 --- a/tests/components/speaker/test.esp32-idf.yaml +++ b/tests/components/speaker/test.esp32-idf.yaml @@ -1,8 +1,15 @@ esphome: on_boot: then: - - speaker.play: [0, 1, 2, 3] - - speaker.stop + - if: + condition: speaker.is_stopped + then: + - speaker.play: [0, 1, 2, 3] + - if: + condition: speaker.is_playing + then: + - speaker.finish: + - speaker.stop: i2s_audio: i2s_lrclk_pin: 16 From ccf57488c5432372dcdb2e87072ed15a9c7953a4 Mon Sep 17 00:00:00 2001 From: Mike La Spina Date: Tue, 13 Aug 2024 23:43:35 -0500 Subject: [PATCH 12/12] Correct offset calibration (#7228) Co-authored-by: descipher Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/atm90e32/__init__.py | 7 ++ esphome/components/atm90e32/atm90e32.cpp | 84 +++++++++++++++---- esphome/components/atm90e32/atm90e32.h | 27 ++++-- esphome/components/atm90e32/atm90e32_reg.h | 2 + .../components/atm90e32/button/__init__.py | 43 ++++++++++ .../atm90e32/button/atm90e32_button.cpp | 20 +++++ .../atm90e32/button/atm90e32_button.h | 27 ++++++ esphome/components/atm90e32/sensor.py | 26 +++--- tests/components/atm90e32/test.esp32-ard.yaml | 9 ++ .../atm90e32/test.esp32-c3-ard.yaml | 9 ++ .../atm90e32/test.esp32-c3-idf.yaml | 9 ++ tests/components/atm90e32/test.esp32-idf.yaml | 9 ++ .../components/atm90e32/test.esp8266-ard.yaml | 40 +++++++++ .../components/atm90e32/test.rp2040-ard.yaml | 9 ++ 15 files changed, 288 insertions(+), 34 deletions(-) create mode 100644 esphome/components/atm90e32/button/__init__.py create mode 100644 esphome/components/atm90e32/button/atm90e32_button.cpp create mode 100644 esphome/components/atm90e32/button/atm90e32_button.h diff --git a/CODEOWNERS b/CODEOWNERS index 3ea9c75ac2..1236c8d842 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -46,6 +46,7 @@ esphome/components/async_tcp/* @OttoWinter esphome/components/at581x/* @X-Ryl669 esphome/components/atc_mithermometer/* @ahpohl esphome/components/atm90e26/* @danieltwagner +esphome/components/atm90e32/* @circuitsetup @descipher esphome/components/b_parasite/* @rbaron esphome/components/ballu/* @bazuchan esphome/components/bang_bang/* @OttoWinter diff --git a/esphome/components/atm90e32/__init__.py b/esphome/components/atm90e32/__init__.py index e69de29bb2..8ce95be489 100644 --- a/esphome/components/atm90e32/__init__.py +++ b/esphome/components/atm90e32/__init__.py @@ -0,0 +1,7 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@circuitsetup", "@descipher"] + +atm90e32_ns = cg.esphome_ns.namespace("atm90e32") + +CONF_ATM90E32_ID = "atm90e32_id" diff --git a/esphome/components/atm90e32/atm90e32.cpp b/esphome/components/atm90e32/atm90e32.cpp index e27459b18a..43647b1855 100644 --- a/esphome/components/atm90e32/atm90e32.cpp +++ b/esphome/components/atm90e32/atm90e32.cpp @@ -132,10 +132,77 @@ void ATM90E32Component::update() { this->status_clear_warning(); } +void ATM90E32Component::restore_calibrations_() { + if (enable_offset_calibration_) { + this->pref_.load(&this->offset_phase_); + } +}; + +void ATM90E32Component::run_offset_calibrations() { + // Run the calibrations and + // Setup voltage and current calibration offsets for PHASE A + this->offset_phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA); + this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_; + this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // C Voltage offset + this->offset_phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA); + this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_; + this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // C Current offset + // Setup voltage and current calibration offsets for PHASE B + this->offset_phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB); + this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_; + this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // C Voltage offset + this->offset_phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB); + this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_; + this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // C Current offset + // Setup voltage and current calibration offsets for PHASE C + this->offset_phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC); + this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_; + this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset + this->offset_phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC); + this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_; + this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset + this->pref_.save(&this->offset_phase_); + ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_, + this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_); + ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_, + this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_); +} + +void ATM90E32Component::clear_offset_calibrations() { + // Clear the calibrations and + this->offset_phase_[PHASEA].voltage_offset_ = 0; + this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_; + this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // C Voltage offset + this->offset_phase_[PHASEA].current_offset_ = 0; + this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_; + this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // C Current offset + this->offset_phase_[PHASEB].voltage_offset_ = 0; + this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_; + this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // C Voltage offset + this->offset_phase_[PHASEB].current_offset_ = 0; + this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_; + this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // C Current offset + this->offset_phase_[PHASEC].voltage_offset_ = 0; + this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_; + this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset + this->offset_phase_[PHASEC].current_offset_ = 0; + this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_; + this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset + this->pref_.save(&this->offset_phase_); + ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_, + this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_); + ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_, + this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_); +} + void ATM90E32Component::setup() { ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component..."); this->spi_setup(); - + if (this->enable_offset_calibration_) { + uint32_t hash = fnv1_hash(App.get_friendly_name()); + this->pref_ = global_preferences->make_preference(hash, true); + this->restore_calibrations_(); + } uint16_t mmode0 = 0x87; // 3P4W 50Hz if (line_freq_ == 60) { mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz @@ -167,27 +234,12 @@ void ATM90E32Component::setup() { this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50% this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750 this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10% - // Setup voltage and current calibration offsets for PHASE A - this->phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA); - this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // A Voltage offset - this->phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA); - this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // A Current offset // Setup voltage and current gain for PHASE A this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[PHASEA].voltage_gain_); // A Voltage rms gain this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_); // A line current gain - // Setup voltage and current calibration offsets for PHASE B - this->phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB); - this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // B Voltage offset - this->phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB); - this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // B Current offset // Setup voltage and current gain for PHASE B this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[PHASEB].voltage_gain_); // B Voltage rms gain this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_); // B line current gain - // Setup voltage and current calibration offsets for PHASE C - this->phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC); - this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset - this->phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC); - this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset // Setup voltage and current gain for PHASE C this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[PHASEC].voltage_gain_); // C Voltage rms gain this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_); // C line current gain diff --git a/esphome/components/atm90e32/atm90e32.h b/esphome/components/atm90e32/atm90e32.h index 0a334dbe8b..35c61d1e05 100644 --- a/esphome/components/atm90e32/atm90e32.h +++ b/esphome/components/atm90e32/atm90e32.h @@ -1,9 +1,12 @@ #pragma once -#include "esphome/core/component.h" +#include "atm90e32_reg.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/spi/spi.h" -#include "atm90e32_reg.h" +#include "esphome/core/application.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/preferences.h" namespace esphome { namespace atm90e32 { @@ -20,7 +23,6 @@ class ATM90E32Component : public PollingComponent, void dump_config() override; float get_setup_priority() const override; void update() override; - void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; } void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; } void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; } @@ -48,9 +50,11 @@ class ATM90E32Component : public PollingComponent, void set_line_freq(int freq) { line_freq_ = freq; } void set_current_phases(int phases) { current_phases_ = phases; } void set_pga_gain(uint16_t gain) { pga_gain_ = gain; } + void run_offset_calibrations(); + void clear_offset_calibrations(); + void set_enable_offset_calibration(bool flag) { enable_offset_calibration_ = flag; } uint16_t calibrate_voltage_offset_phase(uint8_t /*phase*/); uint16_t calibrate_current_offset_phase(uint8_t /*phase*/); - int32_t last_periodic_millis = millis(); protected: @@ -83,10 +87,11 @@ class ATM90E32Component : public PollingComponent, float get_chip_temperature_(); bool get_publish_interval_flag_() { return publish_interval_flag_; }; void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; }; + void restore_calibrations_(); struct ATM90E32Phase { - uint16_t voltage_gain_{7305}; - uint16_t ct_gain_{27961}; + uint16_t voltage_gain_{0}; + uint16_t ct_gain_{0}; uint16_t voltage_offset_{0}; uint16_t current_offset_{0}; float voltage_{0}; @@ -114,13 +119,21 @@ class ATM90E32Component : public PollingComponent, uint32_t cumulative_reverse_active_energy_{0}; } phase_[3]; + struct Calibration { + uint16_t voltage_offset_{0}; + uint16_t current_offset_{0}; + } offset_phase_[3]; + + ESPPreferenceObject pref_; + sensor::Sensor *freq_sensor_{nullptr}; sensor::Sensor *chip_temperature_sensor_{nullptr}; uint16_t pga_gain_{0x15}; int line_freq_{60}; int current_phases_{3}; - bool publish_interval_flag_{true}; + bool publish_interval_flag_{false}; bool peak_current_signed_{false}; + bool enable_offset_calibration_{false}; }; } // namespace atm90e32 diff --git a/esphome/components/atm90e32/atm90e32_reg.h b/esphome/components/atm90e32/atm90e32_reg.h index dac62aa6b4..954fb42e79 100644 --- a/esphome/components/atm90e32/atm90e32_reg.h +++ b/esphome/components/atm90e32/atm90e32_reg.h @@ -1,5 +1,7 @@ #pragma once +#include + namespace esphome { namespace atm90e32 { diff --git a/esphome/components/atm90e32/button/__init__.py b/esphome/components/atm90e32/button/__init__.py new file mode 100644 index 0000000000..931346b386 --- /dev/null +++ b/esphome/components/atm90e32/button/__init__.py @@ -0,0 +1,43 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_CHIP, ICON_SCALE + +from .. import atm90e32_ns +from ..sensor import ATM90E32Component + +CONF_RUN_OFFSET_CALIBRATION = "run_offset_calibration" +CONF_CLEAR_OFFSET_CALIBRATION = "clear_offset_calibration" + +ATM90E32CalibrationButton = atm90e32_ns.class_( + "ATM90E32CalibrationButton", + button.Button, +) +ATM90E32ClearCalibrationButton = atm90e32_ns.class_( + "ATM90E32ClearCalibrationButton", + button.Button, +) + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component), + cv.Optional(CONF_RUN_OFFSET_CALIBRATION): button.button_schema( + ATM90E32CalibrationButton, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_SCALE, + ), + cv.Optional(CONF_CLEAR_OFFSET_CALIBRATION): button.button_schema( + ATM90E32ClearCalibrationButton, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_CHIP, + ), +} + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_ID]) + if run_offset := config.get(CONF_RUN_OFFSET_CALIBRATION): + b = await button.new_button(run_offset) + await cg.register_parented(b, parent) + if clear_offset := config.get(CONF_CLEAR_OFFSET_CALIBRATION): + b = await button.new_button(clear_offset) + await cg.register_parented(b, parent) diff --git a/esphome/components/atm90e32/button/atm90e32_button.cpp b/esphome/components/atm90e32/button/atm90e32_button.cpp new file mode 100644 index 0000000000..00715b61dd --- /dev/null +++ b/esphome/components/atm90e32/button/atm90e32_button.cpp @@ -0,0 +1,20 @@ +#include "atm90e32_button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace atm90e32 { + +static const char *const TAG = "atm90e32.button"; + +void ATM90E32CalibrationButton::press_action() { + ESP_LOGI(TAG, "Running offset calibrations, Note: CTs and ACVs must be 0 during this process..."); + this->parent_->run_offset_calibrations(); +} + +void ATM90E32ClearCalibrationButton::press_action() { + ESP_LOGI(TAG, "Offset calibrations cleared."); + this->parent_->clear_offset_calibrations(); +} + +} // namespace atm90e32 +} // namespace esphome diff --git a/esphome/components/atm90e32/button/atm90e32_button.h b/esphome/components/atm90e32/button/atm90e32_button.h new file mode 100644 index 0000000000..0617099457 --- /dev/null +++ b/esphome/components/atm90e32/button/atm90e32_button.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/atm90e32/atm90e32.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace atm90e32 { + +class ATM90E32CalibrationButton : public button::Button, public Parented { + public: + ATM90E32CalibrationButton() = default; + + protected: + void press_action() override; +}; + +class ATM90E32ClearCalibrationButton : public button::Button, public Parented { + public: + ATM90E32ClearCalibrationButton() = default; + + protected: + void press_action() override; +}; + +} // namespace atm90e32 +} // namespace esphome diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 2bc7f0498d..be2196223c 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -1,21 +1,21 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import sensor, spi +import esphome.config_validation as cv from esphome.const import ( - CONF_ID, - CONF_REACTIVE_POWER, - CONF_VOLTAGE, + CONF_APPARENT_POWER, CONF_CURRENT, + CONF_FORWARD_ACTIVE_ENERGY, + CONF_FREQUENCY, + CONF_ID, CONF_PHASE_A, + CONF_PHASE_ANGLE, CONF_PHASE_B, CONF_PHASE_C, - CONF_PHASE_ANGLE, CONF_POWER, CONF_POWER_FACTOR, - CONF_APPARENT_POWER, - CONF_FREQUENCY, - CONF_FORWARD_ACTIVE_ENERGY, + CONF_REACTIVE_POWER, CONF_REVERSE_ACTIVE_ENERGY, + CONF_VOLTAGE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, @@ -23,13 +23,13 @@ from esphome.const import ( DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, ENTITY_CATEGORY_DIAGNOSTIC, - ICON_LIGHTBULB, ICON_CURRENT_AC, + ICON_LIGHTBULB, STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, - UNIT_DEGREES, UNIT_CELSIUS, + UNIT_DEGREES, UNIT_HERTZ, UNIT_VOLT, UNIT_VOLT_AMPS_REACTIVE, @@ -37,6 +37,8 @@ from esphome.const import ( UNIT_WATT_HOURS, ) +from . import atm90e32_ns + CONF_LINE_FREQUENCY = "line_frequency" CONF_CHIP_TEMPERATURE = "chip_temperature" CONF_GAIN_PGA = "gain_pga" @@ -46,6 +48,7 @@ CONF_GAIN_CT = "gain_ct" CONF_HARMONIC_POWER = "harmonic_power" CONF_PEAK_CURRENT = "peak_current" CONF_PEAK_CURRENT_SIGNED = "peak_current_signed" +CONF_ENABLE_OFFSET_CALIBRATION = "enable_offset_calibration" UNIT_DEG = "degrees" LINE_FREQS = { "50HZ": 50, @@ -61,7 +64,6 @@ PGA_GAINS = { "4X": 0x2A, } -atm90e32_ns = cg.esphome_ns.namespace("atm90e32") ATM90E32Component = atm90e32_ns.class_( "ATM90E32Component", cg.PollingComponent, spi.SPIDevice ) @@ -164,6 +166,7 @@ CONFIG_SCHEMA = ( ), cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True), cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean, + cv.Optional(CONF_ENABLE_OFFSET_CALIBRATION, default=False): cv.boolean, } ) .extend(cv.polling_component_schema("60s")) @@ -227,3 +230,4 @@ async def to_code(config): cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES])) cg.add(var.set_pga_gain(config[CONF_GAIN_PGA])) cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED])) + cg.add(var.set_enable_offset_calibration(config[CONF_ENABLE_OFFSET_CALIBRATION])) diff --git a/tests/components/atm90e32/test.esp32-ard.yaml b/tests/components/atm90e32/test.esp32-ard.yaml index 131270f8ad..3bdc2bcec6 100644 --- a/tests/components/atm90e32/test.esp32-ard.yaml +++ b/tests/components/atm90e32/test.esp32-ard.yaml @@ -7,6 +7,7 @@ spi: sensor: - platform: atm90e32 cs_pin: 13 + id: chip1 phase_a: voltage: name: EMON Line Voltage A @@ -49,3 +50,11 @@ sensor: line_frequency: 60Hz current_phases: 3 gain_pga: 2X + enable_offset_calibration: True +button: + - platform: atm90e32 + id: chip1 + run_offset_calibration: + name: "Chip1 - Run Offset Calibration" + clear_offset_calibration: + name: "Chip1 - Clear Offset Calibration" diff --git a/tests/components/atm90e32/test.esp32-c3-ard.yaml b/tests/components/atm90e32/test.esp32-c3-ard.yaml index 263fb6d24e..9ec0037a61 100644 --- a/tests/components/atm90e32/test.esp32-c3-ard.yaml +++ b/tests/components/atm90e32/test.esp32-c3-ard.yaml @@ -7,6 +7,7 @@ spi: sensor: - platform: atm90e32 cs_pin: 8 + id: chip1 phase_a: voltage: name: EMON Line Voltage A @@ -49,3 +50,11 @@ sensor: line_frequency: 60Hz current_phases: 3 gain_pga: 2X + enable_offset_calibration: True +button: + - platform: atm90e32 + id: chip1 + run_offset_calibration: + name: "Chip1 - Run Offset Calibration" + clear_offset_calibration: + name: "Chip1 - Clear Offset Calibration" diff --git a/tests/components/atm90e32/test.esp32-c3-idf.yaml b/tests/components/atm90e32/test.esp32-c3-idf.yaml index 263fb6d24e..9ec0037a61 100644 --- a/tests/components/atm90e32/test.esp32-c3-idf.yaml +++ b/tests/components/atm90e32/test.esp32-c3-idf.yaml @@ -7,6 +7,7 @@ spi: sensor: - platform: atm90e32 cs_pin: 8 + id: chip1 phase_a: voltage: name: EMON Line Voltage A @@ -49,3 +50,11 @@ sensor: line_frequency: 60Hz current_phases: 3 gain_pga: 2X + enable_offset_calibration: True +button: + - platform: atm90e32 + id: chip1 + run_offset_calibration: + name: "Chip1 - Run Offset Calibration" + clear_offset_calibration: + name: "Chip1 - Clear Offset Calibration" diff --git a/tests/components/atm90e32/test.esp32-idf.yaml b/tests/components/atm90e32/test.esp32-idf.yaml index 131270f8ad..3bdc2bcec6 100644 --- a/tests/components/atm90e32/test.esp32-idf.yaml +++ b/tests/components/atm90e32/test.esp32-idf.yaml @@ -7,6 +7,7 @@ spi: sensor: - platform: atm90e32 cs_pin: 13 + id: chip1 phase_a: voltage: name: EMON Line Voltage A @@ -49,3 +50,11 @@ sensor: line_frequency: 60Hz current_phases: 3 gain_pga: 2X + enable_offset_calibration: True +button: + - platform: atm90e32 + id: chip1 + run_offset_calibration: + name: "Chip1 - Run Offset Calibration" + clear_offset_calibration: + name: "Chip1 - Clear Offset Calibration" diff --git a/tests/components/atm90e32/test.esp8266-ard.yaml b/tests/components/atm90e32/test.esp8266-ard.yaml index e8e2abc1a9..fbb3368efa 100644 --- a/tests/components/atm90e32/test.esp8266-ard.yaml +++ b/tests/components/atm90e32/test.esp8266-ard.yaml @@ -7,6 +7,7 @@ spi: sensor: - platform: atm90e32 cs_pin: 5 + id: chip1 phase_a: voltage: name: EMON Line Voltage A @@ -49,3 +50,42 @@ sensor: line_frequency: 60Hz current_phases: 3 gain_pga: 2X + enable_offset_calibration: True + - platform: atm90e32 + cs_pin: 4 + id: chip2 + phase_a: + voltage: + name: EMON Line Voltage A + current: + name: EMON CT1 Current + power: + name: EMON Active Power CT1 + reactive_power: + name: EMON Reactive Power CT1 + power_factor: + name: EMON Power Factor CT1 + gain_voltage: 7305 + gain_ct: 27961 + phase_c: + voltage: + name: EMON Line Voltage C + current: + name: EMON CT2 Current + power: + name: EMON Active Power CT2 + reactive_power: + name: EMON Reactive Power CT2 + power_factor: + name: EMON Power Factor CT2 + gain_voltage: 7305 + gain_ct: 27961 + line_frequency: 60Hz + current_phases: 2 +button: + - platform: atm90e32 + id: chip1 + run_offset_calibration: + name: "Chip1 - Run Offset Calibration" + clear_offset_calibration: + name: "Chip1 - Clear Offset Calibration" diff --git a/tests/components/atm90e32/test.rp2040-ard.yaml b/tests/components/atm90e32/test.rp2040-ard.yaml index 525e0b801a..a6b7956da7 100644 --- a/tests/components/atm90e32/test.rp2040-ard.yaml +++ b/tests/components/atm90e32/test.rp2040-ard.yaml @@ -7,6 +7,7 @@ spi: sensor: - platform: atm90e32 cs_pin: 5 + id: chip1 phase_a: voltage: name: EMON Line Voltage A @@ -49,3 +50,11 @@ sensor: line_frequency: 60Hz current_phases: 3 gain_pga: 2X + enable_offset_calibration: True +button: + - platform: atm90e32 + id: chip1 + run_offset_calibration: + name: "Chip1 - Run Offset Calibration" + clear_offset_calibration: + name: "Chip1 - Clear Offset Calibration"