From 92ae506ffb0c04d91cad1318fa13847325ed05a6 Mon Sep 17 00:00:00 2001 From: Angel Nunez Mencias Date: Wed, 28 Aug 2024 01:40:21 +0200 Subject: [PATCH 01/55] Add WS2811 to esp32_rmt_led_strip (#7353) --- esphome/components/esp32_rmt_led_strip/light.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index 4a04918275..1e3c2d4f72 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -48,6 +48,7 @@ class LEDStripTimings: CHIPSETS = { + "WS2811": LEDStripTimings(300, 1090, 1090, 320, 0, 300000), "WS2812": LEDStripTimings(400, 1000, 1000, 400, 0, 0), "SK6812": LEDStripTimings(300, 900, 600, 600, 0, 0), "APA106": LEDStripTimings(350, 1360, 1360, 350, 0, 0), From 388abaf09f8a5d2da7e4b8355738dad8852b752f Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 23 Aug 2024 04:56:53 +1000 Subject: [PATCH 02/55] [lvgl] Bug fixes (#7338) --- esphome/components/lvgl/automation.py | 16 +++++++++++++--- esphome/components/lvgl/lvgl_esphome.cpp | 7 +++++++ esphome/components/lvgl/lvgl_esphome.h | 1 + esphome/components/lvgl/widgets/__init__.py | 6 ++++++ esphome/components/lvgl/widgets/line.py | 12 +++++++----- esphome/components/lvgl/widgets/msgbox.py | 3 ++- tests/components/lvgl/lvgl-package.yaml | 6 +++++- 7 files changed, 41 insertions(+), 10 deletions(-) diff --git a/esphome/components/lvgl/automation.py b/esphome/components/lvgl/automation.py index a39f589136..efcac977ab 100644 --- a/esphome/components/lvgl/automation.py +++ b/esphome/components/lvgl/automation.py @@ -5,6 +5,7 @@ from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_TIMEOUT +from esphome.cpp_generator import RawExpression from esphome.cpp_types import nullptr from .defines import ( @@ -26,6 +27,7 @@ from .lvcode import ( add_line_marks, lv, lv_add, + lv_expr, lv_obj, lvgl_comp, ) @@ -38,7 +40,13 @@ from .types import ( lv_disp_t, lv_obj_t, ) -from .widgets import Widget, get_widgets, lv_scr_act, set_obj_properties +from .widgets import ( + Widget, + get_widgets, + lv_scr_act, + set_obj_properties, + wait_for_widgets, +) async def action_to_code( @@ -48,10 +56,12 @@ async def action_to_code( template_arg, args, ): + await wait_for_widgets() async with LambdaContext(parameters=args, where=action_id) as context: + with LvConditional(lv_expr.is_pre_initialise()): + context.add(RawExpression("return")) for widget in widgets: - with LvConditional(widget.obj != nullptr): - await action(widget) + await action(widget) var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) return var diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 92f7a880c3..6882986e7c 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -294,6 +294,13 @@ void LvglComponent::loop() { } lv_timer_handler_run_in_period(5); } +bool lv_is_pre_initialise() { + if (!lv_is_initialized()) { + ESP_LOGE(TAG, "LVGL call before component is initialised"); + return true; + } + return false; +} #ifdef USE_LVGL_IMAGE lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc) { diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 3a3d1aa6c5..df3d4aa68c 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -40,6 +40,7 @@ namespace lvgl { extern lv_event_code_t lv_api_event; // NOLINT extern lv_event_code_t lv_update_event; // NOLINT +extern bool lv_is_pre_initialise(); #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 diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index 4abb25c61d..50da6e131d 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -1,3 +1,4 @@ +import asyncio import sys from typing import Any, Union @@ -223,6 +224,11 @@ async def get_widget_(wid: Widget): return await FakeAwaitable(get_widget_generator(wid)) +async def wait_for_widgets(): + while not Widget.widgets_completed: + await asyncio.sleep(0) + + async def get_widgets(config: Union[dict, list], id: str = CONF_ID) -> list[Widget]: if not config: return [] diff --git a/esphome/components/lvgl/widgets/line.py b/esphome/components/lvgl/widgets/line.py index 8ce4b1965f..4c6439fde4 100644 --- a/esphome/components/lvgl/widgets/line.py +++ b/esphome/components/lvgl/widgets/line.py @@ -3,7 +3,7 @@ import functools import esphome.codegen as cg import esphome.config_validation as cv -from ..defines import CONF_MAIN, literal +from ..defines import CONF_MAIN from ..lvcode import lv from ..types import LvType from . import Widget, WidgetType @@ -38,13 +38,15 @@ LINE_SCHEMA = { class LineType(WidgetType): def __init__(self): - super().__init__(CONF_LINE, LvType("lv_line_t"), (CONF_MAIN,), LINE_SCHEMA) + super().__init__( + CONF_LINE, LvType("lv_line_t"), (CONF_MAIN,), LINE_SCHEMA, modify_schema={} + ) async def to_code(self, w: Widget, config): """For a line object, create and add the points""" - data = literal(config[CONF_POINTS]) - points = cg.static_const_array(config[CONF_POINT_LIST_ID], data) - lv.line_set_points(w.obj, points, len(data)) + if data := config.get(CONF_POINTS): + points = cg.static_const_array(config[CONF_POINT_LIST_ID], data) + lv.line_set_points(w.obj, points, len(data)) line_spec = LineType() diff --git a/esphome/components/lvgl/widgets/msgbox.py b/esphome/components/lvgl/widgets/msgbox.py index 63c4326c7c..c377af6bde 100644 --- a/esphome/components/lvgl/widgets/msgbox.py +++ b/esphome/components/lvgl/widgets/msgbox.py @@ -13,7 +13,7 @@ from ..defines import ( TYPE_FLEX, literal, ) -from ..helpers import add_lv_use +from ..helpers import add_lv_use, lvgl_components_required from ..lv_validation import lv_bool, lv_pct, lv_text from ..lvcode import ( EVENT_ARG, @@ -72,6 +72,7 @@ async def msgbox_to_code(conf): *buttonmatrix_spec.get_uses(), *button_spec.get_uses(), ) + lvgl_components_required.add("BUTTONMATRIX") messagebox_id = conf[CONF_ID] outer = lv_Pvariable(lv_obj_t, messagebox_id.id) buttonmatrix = new_Pvariable( diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 800d6eff27..1479ce7358 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -379,6 +379,7 @@ lvgl: format: "bar value %f" args: [x] - line: + id: lv_line_id align: center points: - 5, 5 @@ -387,7 +388,10 @@ lvgl: - 180, 60 - 240, 10 on_click: - lvgl.page.next: + - lvgl.widget.update: + id: lv_line_id + line_color: 0xFFFF + - lvgl.page.next: - switch: align: right_mid - checkbox: From 86777634922a9c1b695d2f3da30630a827ccd2cf Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:09:40 +1000 Subject: [PATCH 03/55] [core] Clean build if the loaded integrations changed (#7344) --- esphome/writer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/writer.py b/esphome/writer.py index c6111cbe3f..57435d3463 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -106,6 +106,8 @@ def storage_should_clean(old: StorageJSON, new: StorageJSON) -> bool: return True if old.build_path != new.build_path: return True + if old.loaded_integrations != new.loaded_integrations: + return True return False @@ -117,7 +119,9 @@ def update_storage_json(): return if storage_should_clean(old, new): - _LOGGER.info("Core config or version changed, cleaning build files...") + _LOGGER.info( + "Core config, version or integrations changed, cleaning build files..." + ) clean_build() new.save(path) From c1774c42c25350cebc4f649184ba9b1374c6397c Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 26 Aug 2024 08:03:25 +1000 Subject: [PATCH 04/55] [lvgl] Fix race condition involving numbers, switches etc. (#7345) --- esphome/components/lvgl/__init__.py | 5 ++++- esphome/components/lvgl/binary_sensor/__init__.py | 3 ++- esphome/components/lvgl/light/__init__.py | 3 ++- esphome/components/lvgl/lvcode.py | 3 +++ esphome/components/lvgl/number/__init__.py | 3 ++- esphome/components/lvgl/select/__init__.py | 3 ++- esphome/components/lvgl/sensor/__init__.py | 3 ++- esphome/components/lvgl/switch/__init__.py | 3 ++- esphome/components/lvgl/text/__init__.py | 3 ++- esphome/components/lvgl/text_sensor/__init__.py | 3 ++- esphome/components/lvgl/widgets/__init__.py | 13 ++++++++++--- 11 files changed, 33 insertions(+), 12 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 7c51d9c70d..ea020435dc 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -266,7 +266,10 @@ async def to_code(config): await add_top_layer(config) await msgboxes_to_code(config) await disp_update(f"{lv_component}->get_disp()", config) - Widget.set_completed() + # At this point only the setup code should be generated + assert LvContext.added_lambda_count == 1 + Widget.set_completed() + async with LvContext(lv_component): await generate_triggers(lv_component) for conf in config.get(CONF_ON_IDLE, ()): templ = await cg.templatable(conf[CONF_TIMEOUT], [], cg.uint32) diff --git a/esphome/components/lvgl/binary_sensor/__init__.py b/esphome/components/lvgl/binary_sensor/__init__.py index 8789a06375..56984405aa 100644 --- a/esphome/components/lvgl/binary_sensor/__init__.py +++ b/esphome/components/lvgl/binary_sensor/__init__.py @@ -10,7 +10,7 @@ from ..defines import CONF_LVGL_ID, CONF_WIDGET from ..lvcode import EVENT_ARG, LambdaContext, LvContext from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, lv_pseudo_button_t -from ..widgets import Widget, get_widgets +from ..widgets import Widget, get_widgets, wait_for_widgets CONFIG_SCHEMA = ( binary_sensor_schema(BinarySensor) @@ -29,6 +29,7 @@ async def to_code(config): widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] assert isinstance(widget, Widget) + await wait_for_widgets() async with LambdaContext(EVENT_ARG) as pressed_ctx: pressed_ctx.add(sensor.publish_state(widget.is_pressed())) async with LvContext(paren) as ctx: diff --git a/esphome/components/lvgl/light/__init__.py b/esphome/components/lvgl/light/__init__.py index 27c160dff6..a0eeded349 100644 --- a/esphome/components/lvgl/light/__init__.py +++ b/esphome/components/lvgl/light/__init__.py @@ -8,7 +8,7 @@ from ..defines import CONF_LVGL_ID from ..lvcode import LvContext from ..schemas import LVGL_SCHEMA from ..types import LvType, lvgl_ns -from ..widgets import get_widgets +from ..widgets import get_widgets, wait_for_widgets lv_led_t = LvType("lv_led_t") LVLight = lvgl_ns.class_("LVLight", LightOutput) @@ -28,5 +28,6 @@ async def to_code(config): paren = await cg.get_variable(config[CONF_LVGL_ID]) widget = await get_widgets(config, CONF_LED) widget = widget[0] + await wait_for_widgets() async with LvContext(paren) as ctx: ctx.add(var.set_obj(widget.obj)) diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index 6d7e364e5d..8d029ce0ca 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -176,6 +176,8 @@ class LvContext(LambdaContext): Code generation into the LVGL initialisation code (called in `setup()`) """ + added_lambda_count = 0 + def __init__(self, lv_component, args=None): self.args = args or LVGL_COMP_ARG super().__init__(parameters=self.args) @@ -183,6 +185,7 @@ class LvContext(LambdaContext): async def add_init_lambda(self): cg.add(self.lv_component.add_init_lambda(await self.get_lambda())) + LvContext.added_lambda_count += 1 async def __aexit__(self, exc_type, exc_val, exc_tb): await super().__aexit__(exc_type, exc_val, exc_tb) diff --git a/esphome/components/lvgl/number/__init__.py b/esphome/components/lvgl/number/__init__.py index 6336bb0632..3e1ede0dec 100644 --- a/esphome/components/lvgl/number/__init__.py +++ b/esphome/components/lvgl/number/__init__.py @@ -16,7 +16,7 @@ from ..lvcode import ( ) from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvNumber, lvgl_ns -from ..widgets import get_widgets +from ..widgets import get_widgets, wait_for_widgets LVGLNumber = lvgl_ns.class_("LVGLNumber", number.Number) @@ -44,6 +44,7 @@ async def to_code(config): step=widget.get_step(), ) + await wait_for_widgets() async with LambdaContext([(cg.float_, "v")]) as control: await widget.set_property( "value", MockObj("v") * MockObj(widget.get_scale()), config[CONF_ANIMATED] diff --git a/esphome/components/lvgl/select/__init__.py b/esphome/components/lvgl/select/__init__.py index b55bde13bc..e77d0cfb32 100644 --- a/esphome/components/lvgl/select/__init__.py +++ b/esphome/components/lvgl/select/__init__.py @@ -15,7 +15,7 @@ from ..lvcode import ( ) from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvSelect, lvgl_ns -from ..widgets import get_widgets +from ..widgets import get_widgets, wait_for_widgets LVGLSelect = lvgl_ns.class_("LVGLSelect", select.Select) @@ -37,6 +37,7 @@ async def to_code(config): options = widget.config.get(CONF_OPTIONS, []) selector = await select.new_select(config, options=options) paren = await cg.get_variable(config[CONF_LVGL_ID]) + await wait_for_widgets() async with LambdaContext(EVENT_ARG) as pub_ctx: pub_ctx.add(selector.publish_index(widget.get_value())) async with LambdaContext([(cg.uint16, "v")]) as control: diff --git a/esphome/components/lvgl/sensor/__init__.py b/esphome/components/lvgl/sensor/__init__.py index 82e21d5e95..a2a2298c27 100644 --- a/esphome/components/lvgl/sensor/__init__.py +++ b/esphome/components/lvgl/sensor/__init__.py @@ -14,7 +14,7 @@ from ..lvcode import ( ) from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvNumber -from ..widgets import Widget, get_widgets +from ..widgets import Widget, get_widgets, wait_for_widgets CONFIG_SCHEMA = ( sensor_schema(Sensor) @@ -33,6 +33,7 @@ async def to_code(config): widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] assert isinstance(widget, Widget) + await wait_for_widgets() async with LambdaContext(EVENT_ARG) as lamb: lv_add(sensor.publish_state(widget.get_value())) async with LvContext(paren, LVGL_COMP_ARG): diff --git a/esphome/components/lvgl/switch/__init__.py b/esphome/components/lvgl/switch/__init__.py index 957fce17ff..f855c2a034 100644 --- a/esphome/components/lvgl/switch/__init__.py +++ b/esphome/components/lvgl/switch/__init__.py @@ -16,7 +16,7 @@ from ..lvcode import ( ) from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LV_STATE, lv_pseudo_button_t, lvgl_ns -from ..widgets import get_widgets +from ..widgets import get_widgets, wait_for_widgets LVGLSwitch = lvgl_ns.class_("LVGLSwitch", Switch) CONFIG_SCHEMA = ( @@ -35,6 +35,7 @@ async def to_code(config): paren = await cg.get_variable(config[CONF_LVGL_ID]) widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] + await wait_for_widgets() async with LambdaContext(EVENT_ARG) as checked_ctx: checked_ctx.add(switch.publish_state(widget.get_value())) async with LambdaContext([(cg.bool_, "v")]) as control: diff --git a/esphome/components/lvgl/text/__init__.py b/esphome/components/lvgl/text/__init__.py index 9ee494d8a0..56fa42e131 100644 --- a/esphome/components/lvgl/text/__init__.py +++ b/esphome/components/lvgl/text/__init__.py @@ -15,7 +15,7 @@ from ..lvcode import ( ) from ..schemas import LVGL_SCHEMA from ..types import LV_EVENT, LvText, lvgl_ns -from ..widgets import get_widgets +from ..widgets import get_widgets, wait_for_widgets LVGLText = lvgl_ns.class_("LVGLText", text.Text) @@ -32,6 +32,7 @@ async def to_code(config): paren = await cg.get_variable(config[CONF_LVGL_ID]) widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] + await wait_for_widgets() async with LambdaContext([(cg.std_string, "text_value")]) as control: await widget.set_property("text", "text_value.c_str())") lv.event_send(widget.obj, API_EVENT, None) diff --git a/esphome/components/lvgl/text_sensor/__init__.py b/esphome/components/lvgl/text_sensor/__init__.py index cab715dce0..ae39eec291 100644 --- a/esphome/components/lvgl/text_sensor/__init__.py +++ b/esphome/components/lvgl/text_sensor/__init__.py @@ -10,7 +10,7 @@ from ..defines import CONF_LVGL_ID, CONF_WIDGET 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 +from ..widgets import get_widgets, wait_for_widgets CONFIG_SCHEMA = ( text_sensor_schema(TextSensor) @@ -28,6 +28,7 @@ async def to_code(config): paren = await cg.get_variable(config[CONF_LVGL_ID]) widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] + await wait_for_widgets() async with LambdaContext(EVENT_ARG) as pressed_ctx: pressed_ctx.add(sensor.publish_state(widget.get_value())) async with LvContext(paren) as ctx: diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index 50da6e131d..17d73c1714 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -1,4 +1,3 @@ -import asyncio import sys from typing import Any, Union @@ -224,9 +223,17 @@ async def get_widget_(wid: Widget): return await FakeAwaitable(get_widget_generator(wid)) +def widgets_wait_generator(): + while True: + if Widget.widgets_completed: + return + yield + + async def wait_for_widgets(): - while not Widget.widgets_completed: - await asyncio.sleep(0) + if Widget.widgets_completed: + return + await FakeAwaitable(widgets_wait_generator()) async def get_widgets(config: Union[dict, list], id: str = CONF_ID) -> list[Widget]: From 9975e8b544d0d2943503b90e75a4723d130e68b9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:20:26 +1200 Subject: [PATCH 05/55] [api] Fix sending the ``once`` flag on ha entity subscription (#7357) --- esphome/components/api/api_connection.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index bd438265d4..195fcab0ab 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -179,6 +179,7 @@ void APIConnection::loop() { SubscribeHomeAssistantStateResponse resp; resp.entity_id = it.entity_id; resp.attribute = it.attribute.value(); + resp.once = it.once; if (this->send_subscribe_home_assistant_state_response(resp)) { state_subs_at_++; } From 28eda4b220519edfd761d65c3d24f0df0aab4f3c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:54:31 +1200 Subject: [PATCH 06/55] Bump version to 2024.8.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f99d442be3..b27949bf29 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.8.0" +__version__ = "2024.8.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From d6df466237142c542d4ea19ddabe0a7a2f7bb898 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:29:41 +1000 Subject: [PATCH 07/55] [lvgl] Add lvgl.widget.focus action and related triggers. (#7315) --- esphome/components/lvgl/__init__.py | 15 +++- esphome/components/lvgl/automation.py | 81 ++++++++++++++++++++- esphome/components/lvgl/defines.py | 5 +- esphome/components/lvgl/lvcode.py | 6 +- esphome/components/lvgl/lvgl_esphome.cpp | 54 ++++++++++++++ esphome/components/lvgl/lvgl_esphome.h | 9 +++ esphome/components/lvgl/schemas.py | 10 +-- esphome/components/lvgl/trigger.py | 5 +- esphome/components/lvgl/types.py | 2 +- esphome/components/lvgl/widgets/__init__.py | 4 +- esphome/components/lvgl/widgets/arc.py | 6 +- esphome/components/lvgl/widgets/page.py | 58 +++++++++++++-- tests/components/lvgl/lvgl-package.yaml | 25 +++++++ 13 files changed, 253 insertions(+), 27 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index ea020435dc..cab6462d1a 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -21,8 +21,8 @@ from esphome.final_validate import full_config from esphome.helpers import write_file_if_changed from . import defines as df, helpers, lv_validation as lvalid -from .automation import disp_update, update_to_code -from .defines import CONF_SKIP +from .automation import disp_update, focused_widgets, update_to_code +from .defines import CONF_ADJUSTABLE, CONF_SKIP from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code from .lv_validation import lv_bool, lv_images_used from .lvcode import LvContext, LvglComponent @@ -67,7 +67,7 @@ from .widgets.lv_bar import bar_spec from .widgets.meter import meter_spec from .widgets.msgbox import MSGBOX_SCHEMA, msgboxes_to_code from .widgets.obj import obj_spec -from .widgets.page import add_pages, page_spec +from .widgets.page import add_pages, generate_page_triggers, page_spec from .widgets.roller import roller_spec from .widgets.slider import slider_spec from .widgets.spinbox import spinbox_spec @@ -182,6 +182,14 @@ def final_validation(config): raise cv.Invalid( "Using RGBA or RGB24 in image config not compatible with LVGL", path ) + for w in focused_widgets: + path = global_config.get_path_for_id(w) + widget_conf = global_config.get_config_for_path(path[:-1]) + if CONF_ADJUSTABLE in widget_conf and not widget_conf[CONF_ADJUSTABLE]: + raise cv.Invalid( + "A non adjustable arc may not be focused", + path, + ) async def to_code(config): @@ -271,6 +279,7 @@ async def to_code(config): Widget.set_completed() async with LvContext(lv_component): await generate_triggers(lv_component) + await generate_page_triggers(lv_component, config) for conf in config.get(CONF_ON_IDLE, ()): templ = await cg.templatable(conf[CONF_TIMEOUT], [], cg.uint32) idle_trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], lv_component, templ) diff --git a/esphome/components/lvgl/automation.py b/esphome/components/lvgl/automation.py index eb1b54e3ec..8138551c30 100644 --- a/esphome/components/lvgl/automation.py +++ b/esphome/components/lvgl/automation.py @@ -4,13 +4,15 @@ from typing import Callable from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_TIMEOUT -from esphome.cpp_generator import RawExpression +from esphome.const import CONF_ACTION, CONF_GROUP, CONF_ID, CONF_TIMEOUT +from esphome.cpp_generator import RawExpression, get_variable from esphome.cpp_types import nullptr from .defines import ( CONF_DISP_BG_COLOR, CONF_DISP_BG_IMAGE, + CONF_EDITING, + CONF_FREEZE, CONF_LVGL_ID, CONF_SHOW_SNOW, literal, @@ -30,6 +32,7 @@ from .lvcode import ( lv_expr, lv_obj, lvgl_comp, + static_cast, ) from .schemas import DISP_BG_SCHEMA, LIST_ACTION_SCHEMA, LVGL_SCHEMA from .types import ( @@ -38,7 +41,9 @@ from .types import ( LvglCondition, ObjUpdateAction, lv_disp_t, + lv_group_t, lv_obj_t, + lv_pseudo_button_t, ) from .widgets import ( Widget, @@ -48,6 +53,9 @@ from .widgets import ( wait_for_widgets, ) +# Record widgets that are used in a focused action here +focused_widgets = set() + async def action_to_code( widgets: list[Widget], @@ -234,3 +242,72 @@ async def obj_show_to_code(config, action_id, template_arg, args): return await action_to_code( await get_widgets(config), do_show, action_id, template_arg, args ) + + +def focused_id(value): + value = cv.use_id(lv_pseudo_button_t)(value) + focused_widgets.add(value) + return value + + +@automation.register_action( + "lvgl.widget.focus", + ObjUpdateAction, + cv.Any( + cv.maybe_simple_value( + { + cv.Optional(CONF_GROUP): cv.use_id(lv_group_t), + cv.Required(CONF_ACTION): cv.one_of( + "MARK", "RESTORE", "NEXT", "PREVIOUS", upper=True + ), + cv.GenerateID(CONF_LVGL_ID): cv.use_id(LvglComponent), + cv.Optional(CONF_FREEZE, default=False): cv.boolean, + }, + key=CONF_ACTION, + ), + cv.maybe_simple_value( + { + cv.Required(CONF_ID): focused_id, + cv.Optional(CONF_FREEZE, default=False): cv.boolean, + cv.Optional(CONF_EDITING, default=False): cv.boolean, + }, + key=CONF_ID, + ), + ), +) +async def widget_focus(config, action_id, template_arg, args): + widget = await get_widgets(config) + if widget: + widget = widget[0] + group = static_cast( + lv_group_t.operator("ptr"), lv_expr.obj_get_group(widget.obj) + ) + elif group := config.get(CONF_GROUP): + group = await get_variable(group) + else: + group = lv_expr.group_get_default() + + async with LambdaContext(parameters=args, where=action_id) as context: + if widget: + lv.group_focus_freeze(group, False) + lv.group_focus_obj(widget.obj) + if config[CONF_EDITING]: + lv.group_set_editing(group, True) + else: + action = config[CONF_ACTION] + lv_comp = await get_variable(config[CONF_LVGL_ID]) + if action == "MARK": + context.add(lv_comp.set_focus_mark(group)) + else: + lv.group_focus_freeze(group, False) + if action == "RESTORE": + context.add(lv_comp.restore_focus_mark(group)) + elif action == "NEXT": + lv.group_focus_next(group) + else: + lv.group_focus_prev(group) + + if config[CONF_FREEZE]: + lv.group_focus_freeze(group, True) + var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda()) + return var diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 7bb1667e77..e05bf52120 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -148,6 +148,7 @@ LV_EVENT_MAP = { "DEFOCUS": "DEFOCUSED", "READY": "READY", "CANCEL": "CANCEL", + "ALL_EVENTS": "ALL", } LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP) @@ -390,6 +391,7 @@ CONF_DEFAULT_FONT = "default_font" CONF_DEFAULT_GROUP = "default_group" CONF_DIR = "dir" CONF_DISPLAYS = "displays" +CONF_EDITING = "editing" CONF_ENCODERS = "encoders" CONF_END_ANGLE = "end_angle" CONF_END_VALUE = "end_value" @@ -401,6 +403,7 @@ CONF_FLEX_ALIGN_MAIN = "flex_align_main" CONF_FLEX_ALIGN_CROSS = "flex_align_cross" CONF_FLEX_ALIGN_TRACK = "flex_align_track" CONF_FLEX_GROW = "flex_grow" +CONF_FREEZE = "freeze" CONF_FULL_REFRESH = "full_refresh" CONF_GRID_CELL_ROW_POS = "grid_cell_row_pos" CONF_GRID_CELL_COLUMN_POS = "grid_cell_column_pos" @@ -428,9 +431,9 @@ CONF_MSGBOXES = "msgboxes" CONF_OBJ = "obj" CONF_OFFSET_X = "offset_x" CONF_OFFSET_Y = "offset_y" +CONF_ONE_CHECKED = "one_checked" CONF_ONE_LINE = "one_line" CONF_ON_SELECT = "on_select" -CONF_ONE_CHECKED = "one_checked" CONF_NEXT = "next" CONF_PAD_ROW = "pad_row" CONF_PAD_COLUMN = "pad_column" diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index 8d029ce0ca..a3d13f7f8c 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -28,7 +28,7 @@ LVGL_COMP = "lv_component" # used as a lambda argument in lvgl_comp() 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")] +EVENT_ARG = [(lv_event_t_ptr, "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. @@ -291,6 +291,10 @@ class LvExpr(MockLv): pass +def static_cast(type, value): + return literal(f"static_cast<{type}>({value})") + + # Top level mock for generic lv_ calls to be recorded lv = MockLv("lv_") # Just generate an expression diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 6882986e7c..89c9828740 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -15,6 +15,60 @@ static void log_cb(const char *buf) { } #endif // LV_USE_LOG +static const char *const EVENT_NAMES[] = { + "NONE", + "PRESSED", + "PRESSING", + "PRESS_LOST", + "SHORT_CLICKED", + "LONG_PRESSED", + "LONG_PRESSED_REPEAT", + "CLICKED", + "RELEASED", + "SCROLL_BEGIN", + "SCROLL_END", + "SCROLL", + "GESTURE", + "KEY", + "FOCUSED", + "DEFOCUSED", + "LEAVE", + "HIT_TEST", + "COVER_CHECK", + "REFR_EXT_DRAW_SIZE", + "DRAW_MAIN_BEGIN", + "DRAW_MAIN", + "DRAW_MAIN_END", + "DRAW_POST_BEGIN", + "DRAW_POST", + "DRAW_POST_END", + "DRAW_PART_BEGIN", + "DRAW_PART_END", + "VALUE_CHANGED", + "INSERT", + "REFRESH", + "READY", + "CANCEL", + "DELETE", + "CHILD_CHANGED", + "CHILD_CREATED", + "CHILD_DELETED", + "SCREEN_UNLOAD_START", + "SCREEN_LOAD_START", + "SCREEN_LOADED", + "SCREEN_UNLOADED", + "SIZE_CHANGED", + "STYLE_CHANGED", + "LAYOUT_CHANGED", + "GET_SELF_SIZE", +}; + +std::string lv_event_code_name_for(uint8_t event_code) { + if (event_code < sizeof(EVENT_NAMES) / sizeof(EVENT_NAMES[0])) { + return EVENT_NAMES[event_code]; + } + return str_sprintf("%2d", event_code); +} static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) { // make sure all coordinates are even if (area->x1 & 1) diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index df3d4aa68c..e248530971 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -40,6 +40,7 @@ namespace lvgl { extern lv_event_code_t lv_api_event; // NOLINT extern lv_event_code_t lv_update_event; // NOLINT +extern std::string lv_event_code_name_for(uint8_t event_code); extern bool lv_is_pre_initialise(); #ifdef USE_LVGL_COLOR inline lv_color_t lv_color_from(Color color) { return lv_color_make(color.red, color.green, color.blue); } @@ -143,6 +144,13 @@ class LvglComponent : public PollingComponent { void show_next_page(lv_scr_load_anim_t anim, uint32_t time); void show_prev_page(lv_scr_load_anim_t anim, uint32_t time); void set_page_wrap(bool wrap) { this->page_wrap_ = wrap; } + void set_focus_mark(lv_group_t *group) { this->focus_marks_[group] = lv_group_get_focused(group); } + void restore_focus_mark(lv_group_t *group) { + auto *mark = this->focus_marks_[group]; + if (mark != nullptr) { + lv_group_focus_obj(mark); + } + } protected: void write_random_(); @@ -158,6 +166,7 @@ class LvglComponent : public PollingComponent { bool show_snow_{}; lv_coord_t snow_line_{}; bool page_wrap_{true}; + std::map focus_marks_{}; std::vector> init_lambdas_; CallbackManager idle_callbacks_{}; diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index e9714e3b1a..548ebda6dc 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -20,7 +20,7 @@ from . import defines as df, lv_validation as lvalid from .defines import CONF_TIME_FORMAT from .helpers import add_lv_use, requires_component, validate_printf from .lv_validation import lv_color, lv_font, lv_image -from .lvcode import LvglComponent +from .lvcode import LvglComponent, lv_event_t_ptr from .types import ( LVEncoderListener, LvType, @@ -215,14 +215,12 @@ def automation_schema(typ: LvType): events = df.LV_EVENT_TRIGGERS + (CONF_ON_VALUE,) else: events = df.LV_EVENT_TRIGGERS - if isinstance(typ, LvType): - template = Trigger.template(typ.get_arg_type()) - else: - template = Trigger.template() + args = [typ.get_arg_type()] if isinstance(typ, LvType) else [] + args.append(lv_event_t_ptr) return { cv.Optional(event): validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(template), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger.template(*args)), } ) for event in events diff --git a/esphome/components/lvgl/trigger.py b/esphome/components/lvgl/trigger.py index ba93aabb2d..5288745fab 100644 --- a/esphome/components/lvgl/trigger.py +++ b/esphome/components/lvgl/trigger.py @@ -19,6 +19,7 @@ from .lvcode import ( LvConditional, lv, lv_add, + lv_event_t_ptr, ) from .types import LV_EVENT from .widgets import widget_map @@ -65,10 +66,10 @@ async def generate_triggers(lv_component): async def add_trigger(conf, lv_component, w, *events): tid = conf[CONF_TRIGGER_ID] trigger = cg.new_Pvariable(tid) - args = w.get_args() + args = w.get_args() + [(lv_event_t_ptr, "event")] value = w.get_value() await automation.build_automation(trigger, args, conf) async with LambdaContext(EVENT_ARG, where=tid) as context: with LvConditional(w.is_selected()): - lv_add(trigger.trigger(value)) + lv_add(trigger.trigger(value, literal("event"))) lv_add(lv_component.add_event_cb(w.obj, await context.get_lambda(), *events)) diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index be17cf62c2..e4735ea58d 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -57,7 +57,7 @@ lv_group_t = cg.global_ns.struct("lv_group_t") LVTouchListener = lvgl_ns.class_("LVTouchListener") LVEncoderListener = lvgl_ns.class_("LVEncoderListener") lv_obj_t = LvType("lv_obj_t") -lv_page_t = cg.global_ns.class_("LvPageType", LvCompound) +lv_page_t = LvType("LvPageType", parents=(LvCompound,)) lv_img_t = LvType("lv_img_t") LV_EVENT = MockObj(base="LV_EVENT_", op="") diff --git a/esphome/components/lvgl/widgets/__init__.py b/esphome/components/lvgl/widgets/__init__.py index 062c268135..ae06bf20b0 100644 --- a/esphome/components/lvgl/widgets/__init__.py +++ b/esphome/components/lvgl/widgets/__init__.py @@ -225,7 +225,7 @@ def get_widget_generator(wid): yield -async def get_widget_(wid: Widget): +async def get_widget_(wid): if obj := widget_map.get(wid): return obj return await FakeAwaitable(get_widget_generator(wid)) @@ -348,8 +348,6 @@ async def set_obj_properties(w: Widget, config): if group := config.get(CONF_GROUP): group = await cg.get_variable(group) lv.group_add_obj(group, w.obj) - flag_clr = set() - flag_set = set() props = parts[CONF_MAIN][CONF_DEFAULT] lambs = {} flag_set = set() diff --git a/esphome/components/lvgl/widgets/arc.py b/esphome/components/lvgl/widgets/arc.py index a6f8918e2f..dc120e4cbb 100644 --- a/esphome/components/lvgl/widgets/arc.py +++ b/esphome/components/lvgl/widgets/arc.py @@ -1,5 +1,6 @@ import esphome.config_validation as cv from esphome.const import ( + CONF_GROUP, CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_MODE, @@ -20,7 +21,7 @@ from ..defines import ( literal, ) from ..lv_validation import angle, get_start_value, lv_float -from ..lvcode import lv, lv_obj +from ..lvcode import lv, lv_expr, lv_obj from ..types import LvNumber, NumberType from . import Widget @@ -69,6 +70,9 @@ class ArcType(NumberType): if config.get(CONF_ADJUSTABLE) is False: lv_obj.remove_style(w.obj, nullptr, literal("LV_PART_KNOB")) w.clear_flag("LV_OBJ_FLAG_CLICKABLE") + elif CONF_GROUP not in config: + # For some reason arc does not get automatically added to the default group + lv.group_add_obj(lv_expr.group_get_default(), w.obj) value = await get_start_value(config) if value is not None: diff --git a/esphome/components/lvgl/widgets/page.py b/esphome/components/lvgl/widgets/page.py index f80d802b33..0e84ab6791 100644 --- a/esphome/components/lvgl/widgets/page.py +++ b/esphome/components/lvgl/widgets/page.py @@ -1,6 +1,7 @@ from esphome import automation, codegen as cg +from esphome.automation import Trigger import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_PAGES, CONF_TIME +from esphome.const import CONF_ID, CONF_PAGES, CONF_TIME, CONF_TRIGGER_ID from ..defines import ( CONF_ANIMATION, @@ -9,12 +10,39 @@ from ..defines import ( CONF_PAGE_WRAP, CONF_SKIP, LV_ANIM, + literal, ) from ..lv_validation import lv_bool, lv_milliseconds -from ..lvcode import LVGL_COMP_ARG, LambdaContext, add_line_marks, lv_add, lvgl_comp +from ..lvcode import ( + EVENT_ARG, + LVGL_COMP_ARG, + LambdaContext, + add_line_marks, + lv_add, + lvgl_comp, +) from ..schemas import LVGL_SCHEMA from ..types import LvglAction, lv_page_t -from . import Widget, WidgetType, add_widgets, set_obj_properties +from . import Widget, WidgetType, add_widgets, get_widgets, set_obj_properties + +CONF_ON_LOAD = "on_load" +CONF_ON_UNLOAD = "on_unload" + +PAGE_SCHEMA = cv.Schema( + { + cv.Optional(CONF_SKIP, default=False): lv_bool, + cv.Optional(CONF_ON_LOAD): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger.template()), + } + ), + cv.Optional(CONF_ON_UNLOAD): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger.template()), + } + ), + } +) class PageType(WidgetType): @@ -23,9 +51,8 @@ class PageType(WidgetType): CONF_PAGE, lv_page_t, (), - { - cv.Optional(CONF_SKIP, default=False): lv_bool, - }, + PAGE_SCHEMA, + modify_schema={}, ) async def to_code(self, w: Widget, config: dict): @@ -39,7 +66,6 @@ SHOW_SCHEMA = LVGL_SCHEMA.extend( } ) - page_spec = PageType() @@ -111,3 +137,21 @@ async def add_pages(lv_component, config): await set_obj_properties(page, config) await set_obj_properties(page, pconf) await add_widgets(page, pconf) + + +async def generate_page_triggers(lv_component, config): + for pconf in config.get(CONF_PAGES, ()): + page = (await get_widgets(pconf))[0] + for ev in (CONF_ON_LOAD, CONF_ON_UNLOAD): + for loaded in pconf.get(ev, ()): + trigger = cg.new_Pvariable(loaded[CONF_TRIGGER_ID]) + await automation.build_automation(trigger, [], loaded) + async with LambdaContext(EVENT_ARG, where=id) as context: + lv_add(trigger.trigger()) + lv_add( + lv_component.add_event_cb( + page.obj, + await context.get_lambda(), + literal(f"LV_EVENT_SCREEN_{ev[3:].upper()}_START"), + ) + ) diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 0e2c37048b..737d8703b0 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -54,6 +54,17 @@ lvgl: long_press_time: 500ms pages: - id: page1 + on_load: + - logger.log: page loaded + - lvgl.widget.focus: + action: restore + on_unload: + - logger.log: page unloaded + - lvgl.widget.focus: mark + on_all_events: + logger.log: + format: "Event %s" + args: ['lv_event_code_name_for(event->code).c_str()'] skip: true layout: type: flex @@ -70,6 +81,10 @@ lvgl: repeat_count: 10 duration: 1s auto_start: true + on_all_events: + logger.log: + format: "Event %s" + args: ['lv_event_code_name_for(event->code).c_str()'] - label: id: hello_label text: Hello world @@ -229,6 +244,16 @@ lvgl: - label: text: Button on_click: + - lvgl.widget.focus: spin_up + - lvgl.widget.focus: next + - lvgl.widget.focus: previous + - lvgl.widget.focus: + action: previous + freeze: true + - lvgl.widget.focus: + id: spin_up + freeze: true + editing: true - lvgl.label.update: id: hello_label bg_color: 0x123456 From 4b2032a98e7b93c359da92af00d0926bb2d9551d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Aug 2024 05:07:31 +1200 Subject: [PATCH 08/55] [datetime] Fix templated args (#7368) --- esphome/components/datetime/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/datetime/__init__.py b/esphome/components/datetime/__init__.py index 4fda97c5bc..5429121d56 100644 --- a/esphome/components/datetime/__init__.py +++ b/esphome/components/datetime/__init__.py @@ -186,7 +186,7 @@ async def datetime_date_set_to_code(config, action_id, template_arg, args): date_config = config[CONF_DATE] if cg.is_template(date_config): - template_ = await cg.templatable(date_config, [], cg.ESPTime) + template_ = await cg.templatable(date_config, args, cg.ESPTime) cg.add(action_var.set_date(template_)) else: date_struct = cg.StructInitializer( @@ -217,7 +217,7 @@ async def datetime_time_set_to_code(config, action_id, template_arg, args): time_config = config[CONF_TIME] if cg.is_template(time_config): - template_ = await cg.templatable(time_config, [], cg.ESPTime) + template_ = await cg.templatable(time_config, args, cg.ESPTime) cg.add(action_var.set_time(template_)) else: time_struct = cg.StructInitializer( @@ -248,7 +248,7 @@ async def datetime_datetime_set_to_code(config, action_id, template_arg, args): datetime_config = config[CONF_DATETIME] if cg.is_template(datetime_config): - template_ = await cg.templatable(datetime_config, [], cg.ESPTime) + template_ = await cg.templatable(datetime_config, args, cg.ESPTime) cg.add(action_var.set_datetime(template_)) else: datetime_struct = cg.StructInitializer( From b3f03c07c615ae072de8fc1c9f7e6171497a7e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20Kry=C5=84ski?= Date: Thu, 29 Aug 2024 02:52:13 +0200 Subject: [PATCH 09/55] esp32_can: suppress compiler warning (#7372) --- esphome/components/esp32_can/esp32_can.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index 79e4b70f97..214b72e864 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -111,6 +111,7 @@ canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) { .flags = flags, .identifier = frame->can_id, .data_length_code = frame->can_data_length_code, + .data = {}, // to suppress warning, data is initialized properly below }; if (!frame->remote_transmission_request) { memcpy(message.data, frame->data, frame->can_data_length_code); From 0375072bdfe57d5f8eb50b622e9ad9d6d298fd4a Mon Sep 17 00:00:00 2001 From: Aiden <37043404+tarontop@users.noreply.github.com> Date: Thu, 29 Aug 2024 08:52:49 +0800 Subject: [PATCH 10/55] Add support for BL0906 energy meter (#7339) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/bl0906/__init__.py | 1 + esphome/components/bl0906/bl0906.cpp | 238 ++++++++++++++++++ esphome/components/bl0906/bl0906.h | 96 +++++++ esphome/components/bl0906/const.py | 4 + esphome/components/bl0906/constants.h | 122 +++++++++ esphome/components/bl0906/sensor.py | 184 ++++++++++++++ tests/components/bl0906/common.yaml | 62 +++++ tests/components/bl0906/test.esp32-ard.yaml | 5 + .../components/bl0906/test.esp32-c3-ard.yaml | 5 + .../components/bl0906/test.esp32-c3-idf.yaml | 5 + tests/components/bl0906/test.esp32-idf.yaml | 5 + tests/components/bl0906/test.esp8266-ard.yaml | 5 + tests/components/bl0906/test.rp2040-ard.yaml | 5 + 14 files changed, 738 insertions(+) create mode 100644 esphome/components/bl0906/__init__.py create mode 100644 esphome/components/bl0906/bl0906.cpp create mode 100644 esphome/components/bl0906/bl0906.h create mode 100644 esphome/components/bl0906/const.py create mode 100644 esphome/components/bl0906/constants.h create mode 100644 esphome/components/bl0906/sensor.py create mode 100644 tests/components/bl0906/common.yaml create mode 100644 tests/components/bl0906/test.esp32-ard.yaml create mode 100644 tests/components/bl0906/test.esp32-c3-ard.yaml create mode 100644 tests/components/bl0906/test.esp32-c3-idf.yaml create mode 100644 tests/components/bl0906/test.esp32-idf.yaml create mode 100644 tests/components/bl0906/test.esp8266-ard.yaml create mode 100644 tests/components/bl0906/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 9159f5f843..40511e2f41 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -58,6 +58,7 @@ esphome/components/beken_spi_led_strip/* @Mat931 esphome/components/bh1750/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/bk72xx/* @kuba2k2 +esphome/components/bl0906/* @athom-tech @jesserockz @tarontop esphome/components/bl0939/* @ziceva esphome/components/bl0940/* @tobias- esphome/components/bl0942/* @dbuezas diff --git a/esphome/components/bl0906/__init__.py b/esphome/components/bl0906/__init__.py new file mode 100644 index 0000000000..b077792604 --- /dev/null +++ b/esphome/components/bl0906/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@athom-tech", "@tarontop", "@jesserockz"] diff --git a/esphome/components/bl0906/bl0906.cpp b/esphome/components/bl0906/bl0906.cpp new file mode 100644 index 0000000000..bddb62ff64 --- /dev/null +++ b/esphome/components/bl0906/bl0906.cpp @@ -0,0 +1,238 @@ +#include "bl0906.h" +#include "constants.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace bl0906 { + +static const char *const TAG = "bl0906"; + +constexpr uint32_t to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; } + +constexpr int32_t to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; } + +// The SUM byte is (Addr+Data_L+Data_M+Data_H)&0xFF negated; +constexpr uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data) { + return (address + data->l + data->m + data->h) ^ 0xFF; +} + +void BL0906::loop() { + if (this->current_channel_ == UINT8_MAX) { + return; + } + + while (this->available()) + this->flush(); + + if (this->current_channel_ == 0) { + // Temperature + this->read_data_(BL0906_TEMPERATURE, BL0906_TREF, this->temperature_sensor_); + } else if (this->current_channel_ == 1) { + this->read_data_(BL0906_I_1_RMS, BL0906_IREF, this->current_1_sensor_); + this->read_data_(BL0906_WATT_1, BL0906_PREF, this->power_1_sensor_); + this->read_data_(BL0906_CF_1_CNT, BL0906_EREF, this->energy_1_sensor_); + } else if (this->current_channel_ == 2) { + this->read_data_(BL0906_I_2_RMS, BL0906_IREF, this->current_2_sensor_); + this->read_data_(BL0906_WATT_2, BL0906_PREF, this->power_2_sensor_); + this->read_data_(BL0906_CF_2_CNT, BL0906_EREF, this->energy_2_sensor_); + } else if (this->current_channel_ == 3) { + this->read_data_(BL0906_I_3_RMS, BL0906_IREF, this->current_3_sensor_); + this->read_data_(BL0906_WATT_3, BL0906_PREF, this->power_3_sensor_); + this->read_data_(BL0906_CF_3_CNT, BL0906_EREF, this->energy_3_sensor_); + } else if (this->current_channel_ == 4) { + this->read_data_(BL0906_I_4_RMS, BL0906_IREF, this->current_4_sensor_); + this->read_data_(BL0906_WATT_4, BL0906_PREF, this->power_4_sensor_); + this->read_data_(BL0906_CF_4_CNT, BL0906_EREF, this->energy_4_sensor_); + } else if (this->current_channel_ == 5) { + this->read_data_(BL0906_I_5_RMS, BL0906_IREF, this->current_5_sensor_); + this->read_data_(BL0906_WATT_5, BL0906_PREF, this->power_5_sensor_); + this->read_data_(BL0906_CF_5_CNT, BL0906_EREF, this->energy_5_sensor_); + } else if (this->current_channel_ == 6) { + this->read_data_(BL0906_I_6_RMS, BL0906_IREF, this->current_6_sensor_); + this->read_data_(BL0906_WATT_6, BL0906_PREF, this->power_6_sensor_); + this->read_data_(BL0906_CF_6_CNT, BL0906_EREF, this->energy_6_sensor_); + } else if (this->current_channel_ == UINT8_MAX - 2) { + // Frequency + this->read_data_(BL0906_FREQUENCY, BL0906_FREF, frequency_sensor_); + // Voltage + this->read_data_(BL0906_V_RMS, BL0906_UREF, voltage_sensor_); + } else if (this->current_channel_ == UINT8_MAX - 1) { + // Total power + this->read_data_(BL0906_WATT_SUM, BL0906_WATT, this->total_power_sensor_); + // Total Energy + this->read_data_(BL0906_CF_SUM_CNT, BL0906_CF, this->total_energy_sensor_); + } else { + this->current_channel_ = UINT8_MAX - 2; // Go to frequency and voltage + return; + } + this->current_channel_++; + this->handle_actions_(); +} + +void BL0906::setup() { + while (this->available()) + this->flush(); + this->write_array(USR_WRPROT_WITABLE, sizeof(USR_WRPROT_WITABLE)); + // Calibration (1: register address; 2: value before calibration; 3: value after calibration) + this->bias_correction_(BL0906_RMSOS_1, 0.01600, 0); // Calibration current_1 + this->bias_correction_(BL0906_RMSOS_2, 0.01500, 0); + this->bias_correction_(BL0906_RMSOS_3, 0.01400, 0); + this->bias_correction_(BL0906_RMSOS_4, 0.01300, 0); + this->bias_correction_(BL0906_RMSOS_5, 0.01200, 0); + this->bias_correction_(BL0906_RMSOS_6, 0.01200, 0); // Calibration current_6 + + this->write_array(USR_WRPROT_ONLYREAD, sizeof(USR_WRPROT_ONLYREAD)); +} + +void BL0906::update() { this->current_channel_ = 0; } + +size_t BL0906::enqueue_action_(ActionCallbackFuncPtr function) { + this->action_queue_.push_back(function); + return this->action_queue_.size(); +} + +void BL0906::handle_actions_() { + if (this->action_queue_.empty()) { + return; + } + ActionCallbackFuncPtr ptr_func = nullptr; + for (int i = 0; i < this->action_queue_.size(); i++) { + ptr_func = this->action_queue_[i]; + if (ptr_func) { + ESP_LOGI(TAG, "HandleActionCallback[%d]...", i); + (this->*ptr_func)(); + } + } + + while (this->available()) { + this->read(); + } + + this->action_queue_.clear(); +} + +// Reset energy +void BL0906::reset_energy_() { + this->write_array(BL0906_INIT[0], 6); + delay(1); + this->flush(); + + ESP_LOGW(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_INIT[0][0], BL0906_INIT[0][1], BL0906_INIT[0][2], + BL0906_INIT[0][3], BL0906_INIT[0][4], BL0906_INIT[0][5]); +} + +// Read data +void BL0906::read_data_(const uint8_t address, const float reference, sensor::Sensor *sensor) { + if (sensor == nullptr) { + return; + } + DataPacket buffer; + ube24_t data_u24; + sbe24_t data_s24; + float value = 0; + + bool signed_result = reference == BL0906_TREF || reference == BL0906_WATT || reference == BL0906_PREF; + + this->write_byte(BL0906_READ_COMMAND); + this->write_byte(address); + if (this->read_array((uint8_t *) &buffer, sizeof(buffer) - 1)) { + if (bl0906_checksum(address, &buffer) == buffer.checksum) { + if (signed_result) { + data_s24.l = buffer.l; + data_s24.m = buffer.m; + data_s24.h = buffer.h; + } else { + data_u24.l = buffer.l; + data_u24.m = buffer.m; + data_u24.h = buffer.h; + } + } else { + ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); + while (read() >= 0) + ; + return; + } + } + // Power + if (reference == BL0906_PREF) { + value = (float) to_int32_t(data_s24) * reference; + } + + // Total power + if (reference == BL0906_WATT) { + value = (float) to_int32_t(data_s24) * reference; + } + + // Voltage, current, power, total power + if (reference == BL0906_UREF || reference == BL0906_IREF || reference == BL0906_EREF || reference == BL0906_CF) { + value = (float) to_uint32_t(data_u24) * reference; + } + + // Frequency + if (reference == BL0906_FREF) { + value = reference / (float) to_uint32_t(data_u24); + } + // Chip temperature + if (reference == BL0906_TREF) { + value = (float) to_int32_t(data_s24); + value = (value - 64) * 12.5 / 59 - 40; + } + sensor->publish_state(value); +} + +// RMS offset correction +void BL0906::bias_correction_(uint8_t address, float measurements, float correction) { + DataPacket data; + float ki = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097; // Current coefficient + float i_rms0 = measurements * ki; + float i_rms = correction * ki; + int32_t value = (i_rms * i_rms - i_rms0 * i_rms0) / 256; + data.l = value << 24 >> 24; + data.m = value << 16 >> 24; + if (value < 0) { + data.h = (value << 8 >> 24) | 0b10000000; + } + data.address = bl0906_checksum(address, &data); + ESP_LOGV(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_WRITE_COMMAND, address, data.l, data.m, data.h, data.address); + this->write_byte(BL0906_WRITE_COMMAND); + this->write_byte(address); + this->write_byte(data.l); + this->write_byte(data.m); + this->write_byte(data.h); + this->write_byte(data.address); +} + +void BL0906::dump_config() { + ESP_LOGCONFIG(TAG, "BL0906:"); + LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); + + LOG_SENSOR(" ", "Current1", this->current_1_sensor_); + LOG_SENSOR(" ", "Current2", this->current_2_sensor_); + LOG_SENSOR(" ", "Current3", this->current_3_sensor_); + LOG_SENSOR(" ", "Current4", this->current_4_sensor_); + LOG_SENSOR(" ", "Current5", this->current_5_sensor_); + LOG_SENSOR(" ", "Current6", this->current_6_sensor_); + + LOG_SENSOR(" ", "Power1", this->power_1_sensor_); + LOG_SENSOR(" ", "Power2", this->power_2_sensor_); + LOG_SENSOR(" ", "Power3", this->power_3_sensor_); + LOG_SENSOR(" ", "Power4", this->power_4_sensor_); + LOG_SENSOR(" ", "Power5", this->power_5_sensor_); + LOG_SENSOR(" ", "Power6", this->power_6_sensor_); + + LOG_SENSOR(" ", "Energy1", this->energy_1_sensor_); + LOG_SENSOR(" ", "Energy2", this->energy_2_sensor_); + LOG_SENSOR(" ", "Energy3", this->energy_3_sensor_); + LOG_SENSOR(" ", "Energy4", this->energy_4_sensor_); + LOG_SENSOR(" ", "Energy5", this->energy_5_sensor_); + LOG_SENSOR(" ", "Energy6", this->energy_6_sensor_); + + LOG_SENSOR(" ", "Total Power", this->total_power_sensor_); + LOG_SENSOR(" ", "Total Energy", this->total_energy_sensor_); + LOG_SENSOR(" ", "Frequency", this->frequency_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); +} + +} // namespace bl0906 +} // namespace esphome diff --git a/esphome/components/bl0906/bl0906.h b/esphome/components/bl0906/bl0906.h new file mode 100644 index 0000000000..5a9ad0f028 --- /dev/null +++ b/esphome/components/bl0906/bl0906.h @@ -0,0 +1,96 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/datatypes.h" + +// https://www.belling.com.cn/media/file_object/bel_product/BL0906/datasheet/BL0906_V1.02_cn.pdf +// https://www.belling.com.cn/media/file_object/bel_product/BL0906/guide/BL0906%20APP%20Note_V1.02.pdf + +namespace esphome { +namespace bl0906 { + +struct DataPacket { // NOLINT(altera-struct-pack-align) + uint8_t l{0}; + uint8_t m{0}; + uint8_t h{0}; + uint8_t checksum; // checksum + uint8_t address; +} __attribute__((packed)); + +struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align) + uint8_t l{0}; + uint8_t m{0}; + uint8_t h{0}; +} __attribute__((packed)); + +struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align) + uint8_t l{0}; + uint8_t m{0}; + int8_t h{0}; +} __attribute__((packed)); + +template class ResetEnergyAction; + +class BL0906; + +using ActionCallbackFuncPtr = void (BL0906::*)(); + +class BL0906 : public PollingComponent, public uart::UARTDevice { + SUB_SENSOR(voltage) + SUB_SENSOR(current_1) + SUB_SENSOR(current_2) + SUB_SENSOR(current_3) + SUB_SENSOR(current_4) + SUB_SENSOR(current_5) + SUB_SENSOR(current_6) + SUB_SENSOR(power_1) + SUB_SENSOR(power_2) + SUB_SENSOR(power_3) + SUB_SENSOR(power_4) + SUB_SENSOR(power_5) + SUB_SENSOR(power_6) + SUB_SENSOR(total_power) + SUB_SENSOR(energy_1) + SUB_SENSOR(energy_2) + SUB_SENSOR(energy_3) + SUB_SENSOR(energy_4) + SUB_SENSOR(energy_5) + SUB_SENSOR(energy_6) + SUB_SENSOR(total_energy) + SUB_SENSOR(frequency) + SUB_SENSOR(temperature) + + public: + void loop() override; + + void update() override; + void setup() override; + void dump_config() override; + + protected: + template friend class ResetEnergyAction; + + void reset_energy_(); + + void read_data_(uint8_t address, float reference, sensor::Sensor *sensor); + + void bias_correction_(uint8_t address, float measurements, float correction); + + uint8_t current_channel_{0}; + size_t enqueue_action_(ActionCallbackFuncPtr function); + void handle_actions_(); + + private: + std::vector action_queue_{}; +}; + +template class ResetEnergyAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); } +}; + +} // namespace bl0906 +} // namespace esphome diff --git a/esphome/components/bl0906/const.py b/esphome/components/bl0906/const.py new file mode 100644 index 0000000000..67f21d35b0 --- /dev/null +++ b/esphome/components/bl0906/const.py @@ -0,0 +1,4 @@ +# const.py +ICON_ENERGY = "mdi:lightning-bolt" +ICON_FREQUENCY = "mdi:cosine-wave" +ICON_VOLTAGE = "mdi:sine-wave" diff --git a/esphome/components/bl0906/constants.h b/esphome/components/bl0906/constants.h new file mode 100644 index 0000000000..546916aa3c --- /dev/null +++ b/esphome/components/bl0906/constants.h @@ -0,0 +1,122 @@ +#pragma once +#include + +namespace esphome { +namespace bl0906 { + +// Total power conversion +static const float BL0906_WATT = 16 * 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / + (40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000); +// Total Energy conversion +static const float BL0906_CF = 16 * 4194304 * 0.032768 * 16 / + (3600000 * 16 * + (40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / + (1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000)))); +// Frequency conversion +static const float BL0906_FREF = 10000000; +// Temperature conversion +static const float BL0906_TREF = 12.5 / 59 - 40; +// Current conversion +static const float BL0906_IREF = 1.097 / (12875 * 1 * (5.1 + 5.1) * 1000 / 2000); +// Voltage conversion +static const float BL0906_UREF = 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / (13162 * 1 * 100 * 1000); +// Power conversion +static const float BL0906_PREF = 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / + (40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000); +// Energy conversion +static const float BL0906_EREF = 4194304 * 0.032768 * 16 / + (3600000 * 16 * + (40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / + (1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000)))); +// Current coefficient +static const float BL0906_KI = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097; +// Power coefficient +static const float BL0906_KP = 40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / 1.097 / 1.097 / + (20000 + 20000 + 20000 + 20000 + 20000); + +static const uint8_t USR_WRPROT_WITABLE[6] = {0xCA, 0x9E, 0x55, 0x55, 0x00, 0xB7}; +static const uint8_t USR_WRPROT_ONLYREAD[6] = {0xCA, 0x9E, 0x00, 0x00, 0x00, 0x61}; + +static const uint8_t BL0906_READ_COMMAND = 0x35; +static const uint8_t BL0906_WRITE_COMMAND = 0xCA; + +// Register address +// Voltage +static const uint8_t BL0906_V_RMS = 0x16; + +// Total power +static const uint8_t BL0906_WATT_SUM = 0X2C; + +// Current1~6 +static const uint8_t BL0906_I_1_RMS = 0x0D; // current_1 +static const uint8_t BL0906_I_2_RMS = 0x0E; +static const uint8_t BL0906_I_3_RMS = 0x0F; +static const uint8_t BL0906_I_4_RMS = 0x10; +static const uint8_t BL0906_I_5_RMS = 0x13; +static const uint8_t BL0906_I_6_RMS = 0x14; // current_6 + +// Power1~6 +static const uint8_t BL0906_WATT_1 = 0X23; // power_1 +static const uint8_t BL0906_WATT_2 = 0X24; +static const uint8_t BL0906_WATT_3 = 0X25; +static const uint8_t BL0906_WATT_4 = 0X26; +static const uint8_t BL0906_WATT_5 = 0X29; +static const uint8_t BL0906_WATT_6 = 0X2A; // power_6 + +// Active pulse count, unsigned +static const uint8_t BL0906_CF_1_CNT = 0X30; // Channel_1 +static const uint8_t BL0906_CF_2_CNT = 0X31; +static const uint8_t BL0906_CF_3_CNT = 0X32; +static const uint8_t BL0906_CF_4_CNT = 0X33; +static const uint8_t BL0906_CF_5_CNT = 0X36; +static const uint8_t BL0906_CF_6_CNT = 0X37; // Channel_6 + +// Total active pulse count, unsigned +static const uint8_t BL0906_CF_SUM_CNT = 0X39; + +// Voltage frequency cycle +static const uint8_t BL0906_FREQUENCY = 0X4E; + +// Internal temperature +static const uint8_t BL0906_TEMPERATURE = 0X5E; + +// Calibration register +// RMS gain adjustment register +static const uint8_t BL0906_RMSGN_1 = 0x6D; // Channel_1 +static const uint8_t BL0906_RMSGN_2 = 0x6E; +static const uint8_t BL0906_RMSGN_3 = 0x6F; +static const uint8_t BL0906_RMSGN_4 = 0x70; +static const uint8_t BL0906_RMSGN_5 = 0x73; +static const uint8_t BL0906_RMSGN_6 = 0x74; // Channel_6 + +// RMS offset correction register +static const uint8_t BL0906_RMSOS_1 = 0x78; // Channel_1 +static const uint8_t BL0906_RMSOS_2 = 0x79; +static const uint8_t BL0906_RMSOS_3 = 0x7A; +static const uint8_t BL0906_RMSOS_4 = 0x7B; +static const uint8_t BL0906_RMSOS_5 = 0x7E; +static const uint8_t BL0906_RMSOS_6 = 0x7F; // Channel_6 + +// Active power gain adjustment register +static const uint8_t BL0906_WATTGN_1 = 0xB7; // Channel_1 +static const uint8_t BL0906_WATTGN_2 = 0xB8; +static const uint8_t BL0906_WATTGN_3 = 0xB9; +static const uint8_t BL0906_WATTGN_4 = 0xBA; +static const uint8_t BL0906_WATTGN_5 = 0xBD; +static const uint8_t BL0906_WATTGN_6 = 0xBE; // Channel_6 + +// User write protection setting register, +// You must first write 0x5555 to the write protection setting register before writing to other registers. +static const uint8_t BL0906_USR_WRPROT = 0x9E; + +// Reset Register +static const uint8_t BL0906_SOFT_RESET = 0x9F; + +const uint8_t BL0906_INIT[2][6] = { + // Reset to default + {BL0906_WRITE_COMMAND, BL0906_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x52}, + // Enable User Operation Write + {BL0906_WRITE_COMMAND, BL0906_USR_WRPROT, 0x55, 0x55, 0x00, 0xB7}}; + +} // namespace bl0906 +} // namespace esphome diff --git a/esphome/components/bl0906/sensor.py b/esphome/components/bl0906/sensor.py new file mode 100644 index 0000000000..bc370c9252 --- /dev/null +++ b/esphome/components/bl0906/sensor.py @@ -0,0 +1,184 @@ +from esphome import automation +from esphome.automation import maybe_simple_id +import esphome.codegen as cg +from esphome.components import sensor, uart +import esphome.config_validation as cv +from esphome.const import ( + CONF_CHANNEL, + CONF_CURRENT, + CONF_ENERGY, + CONF_FREQUENCY, + CONF_ID, + CONF_NAME, + CONF_POWER, + CONF_TEMPERATURE, + CONF_TOTAL_POWER, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + ICON_CURRENT_AC, + ICON_POWER, + ICON_THERMOMETER, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_HERTZ, + UNIT_KILOWATT_HOURS, + UNIT_VOLT, + UNIT_WATT, +) + +# Import ICONS not included in esphome's const.py, from the local components const.py +from .const import ICON_ENERGY, ICON_FREQUENCY, ICON_VOLTAGE + +DEPENDENCIES = ["uart"] +AUTO_LOAD = ["bl0906"] +CONF_TOTAL_ENERGY = "total_energy" + +bl0906_ns = cg.esphome_ns.namespace("bl0906") +BL0906 = bl0906_ns.class_("BL0906", cg.PollingComponent, uart.UARTDevice) +ResetEnergyAction = bl0906_ns.class_("ResetEnergyAction", automation.Action) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BL0906), + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + icon=ICON_FREQUENCY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_FREQUENCY, + unit_of_measurement=UNIT_HERTZ, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + unit_of_measurement=UNIT_CELSIUS, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + icon=ICON_VOLTAGE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + unit_of_measurement=UNIT_VOLT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TOTAL_POWER): sensor.sensor_schema( + icon=ICON_POWER, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + unit_of_measurement=UNIT_WATT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TOTAL_ENERGY): sensor.sensor_schema( + icon=ICON_ENERGY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + ), + } + ) + .extend( + cv.Schema( + { + cv.Optional(f"{CONF_CHANNEL}_{i + 1}"): cv.Schema( + { + cv.Optional(CONF_CURRENT): cv.maybe_simple_value( + sensor.sensor_schema( + icon=ICON_CURRENT_AC, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + unit_of_measurement=UNIT_AMPERE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_POWER): cv.maybe_simple_value( + sensor.sensor_schema( + icon=ICON_POWER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + unit_of_measurement=UNIT_WATT, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_ENERGY): cv.maybe_simple_value( + sensor.sensor_schema( + icon=ICON_ENERGY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + unit_of_measurement=UNIT_KILOWATT_HOURS, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + key=CONF_NAME, + ), + } + ) + for i in range(6) + } + ) + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.polling_component_schema("60s")) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "bl0906", baud_rate=19200, require_tx=True, require_rx=True +) + + +@automation.register_action( + "bl0906.reset_energy", + ResetEnergyAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(BL0906), + } + ), +) +async def reset_energy_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + if frequency_config := config.get(CONF_FREQUENCY): + sens = await sensor.new_sensor(frequency_config) + cg.add(var.set_frequency_sensor(sens)) + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + if voltage_config := config.get(CONF_VOLTAGE): + sens = await sensor.new_sensor(voltage_config) + cg.add(var.set_voltage_sensor(sens)) + + for i in range(6): + if channel_config := config.get(f"{CONF_CHANNEL}_{i + 1}"): + if current_config := channel_config.get(CONF_CURRENT): + sens = await sensor.new_sensor(current_config) + cg.add(getattr(var, f"set_current_{i + 1}_sensor")(sens)) + if power_config := channel_config.get(CONF_POWER): + sens = await sensor.new_sensor(power_config) + cg.add(getattr(var, f"set_power_{i + 1}_sensor")(sens)) + if energy_config := channel_config.get(CONF_ENERGY): + sens = await sensor.new_sensor(energy_config) + cg.add(getattr(var, f"set_energy_{i + 1}_sensor")(sens)) + + if total_power_config := config.get(CONF_TOTAL_POWER): + sens = await sensor.new_sensor(total_power_config) + cg.add(var.set_total_power_sensor(sens)) + + if total_energy_config := config.get(CONF_TOTAL_ENERGY): + sens = await sensor.new_sensor(total_energy_config) + cg.add(var.set_total_energy_sensor(sens)) diff --git a/tests/components/bl0906/common.yaml b/tests/components/bl0906/common.yaml new file mode 100644 index 0000000000..944791369c --- /dev/null +++ b/tests/components/bl0906/common.yaml @@ -0,0 +1,62 @@ +uart: + - id: uart_bl0906 + tx_pin: + number: ${tx_pin} + rx_pin: + number: ${rx_pin} + baud_rate: 19200 + +sensor: + - platform: bl0906 + frequency: + name: 'Frequency' + temperature: + name: 'Temperature' + voltage: + name: 'Voltage' + channel_1: + current: + name: 'Current_1' + power: + name: 'Power_1' + energy: + name: 'Energy_1' + channel_2: + current: + name: 'Current_2' + power: + name: 'Power_2' + energy: + name: 'Energy_2' + channel_3: + current: + name: 'Current_3' + power: + name: 'Power_3' + energy: + name: 'Energy_3' + channel_4: + current: + name: 'Current_4' + power: + name: 'Power_4' + energy: + name: 'Energy_4' + channel_5: + current: + name: 'Current_5' + power: + name: 'Power_5' + energy: + name: 'Energy_5' + channel_6: + current: + name: 'Current_6' + power: + name: 'Power_6' + energy: + name: 'Energy_6' + total_energy: + name: 'Total_Energy' + total_power: + name: 'Total_Power' diff --git a/tests/components/bl0906/test.esp32-ard.yaml b/tests/components/bl0906/test.esp32-ard.yaml new file mode 100644 index 0000000000..811f6b72a6 --- /dev/null +++ b/tests/components/bl0906/test.esp32-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO12 + rx_pin: GPIO14 + +<<: !include common.yaml diff --git a/tests/components/bl0906/test.esp32-c3-ard.yaml b/tests/components/bl0906/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..c79d14c740 --- /dev/null +++ b/tests/components/bl0906/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO7 + rx_pin: GPIO8 + +<<: !include common.yaml diff --git a/tests/components/bl0906/test.esp32-c3-idf.yaml b/tests/components/bl0906/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..c79d14c740 --- /dev/null +++ b/tests/components/bl0906/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO7 + rx_pin: GPIO8 + +<<: !include common.yaml diff --git a/tests/components/bl0906/test.esp32-idf.yaml b/tests/components/bl0906/test.esp32-idf.yaml new file mode 100644 index 0000000000..811f6b72a6 --- /dev/null +++ b/tests/components/bl0906/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO12 + rx_pin: GPIO14 + +<<: !include common.yaml diff --git a/tests/components/bl0906/test.esp8266-ard.yaml b/tests/components/bl0906/test.esp8266-ard.yaml new file mode 100644 index 0000000000..3b44f9c9c3 --- /dev/null +++ b/tests/components/bl0906/test.esp8266-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO1 + rx_pin: GPIO3 + +<<: !include common.yaml diff --git a/tests/components/bl0906/test.rp2040-ard.yaml b/tests/components/bl0906/test.rp2040-ard.yaml new file mode 100644 index 0000000000..b516342f3b --- /dev/null +++ b/tests/components/bl0906/test.rp2040-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml From 1922f2bbee79ba1c4830ec286ef7032cd8b7d613 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 29 Aug 2024 10:55:37 +1000 Subject: [PATCH 11/55] [platformio] Add environments for ESP-IDF 5.3 for development (#7371) --- platformio.ini | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/platformio.ini b/platformio.ini index 4a0a3f2ef4..147159a841 100644 --- a/platformio.ini +++ b/platformio.ini @@ -153,6 +153,13 @@ build_flags = -DUSE_ESP32_FRAMEWORK_ESP_IDF extra_scripts = post:esphome/components/esp32/post_build.py.script +; This are common settings for the ESP32 using the latest ESP-IDF version. +[common:esp32-idf-5_3] +extends = common:esp32-idf +platform = platformio/espressif32@6.8.0 +platform_packages = + platformio/framework-espidf@~3.50300.0 + ; These are common settings for the RP2040 using Arduino. [common:rp2040-arduino] extends = common:arduino @@ -229,6 +236,15 @@ build_flags = ${flags:runtime.build_flags} -DUSE_ESP32_VARIANT_ESP32 +[env:esp32-idf-5_3] +extends = common:esp32-idf-5_3 +board = esp32dev +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf +build_flags = + ${common:esp32-idf.build_flags} + ${flags:runtime.build_flags} + -DUSE_ESP32_VARIANT_ESP32 + [env:esp32-idf-tidy] extends = common:esp32-idf board = esp32dev @@ -265,6 +281,15 @@ build_flags = ${flags:runtime.build_flags} -DUSE_ESP32_VARIANT_ESP32C3 +[env:esp32c3-idf-5_3] +extends = common:esp32-idf-5_3 +board = esp32-c3-devkitm-1 +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32c3-idf +build_flags = + ${common:esp32-idf.build_flags} + ${flags:runtime.build_flags} + -DUSE_ESP32_VARIANT_ESP32C3 + [env:esp32c3-idf-tidy] extends = common:esp32-idf board = esp32-c3-devkitm-1 @@ -301,6 +326,15 @@ build_flags = ${flags:runtime.build_flags} -DUSE_ESP32_VARIANT_ESP32S2 +[env:esp32s2-idf-5_3] +extends = common:esp32-idf-5_3 +board = esp32-s2-kaluga-1 +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf +build_flags = + ${common:esp32-idf.build_flags} + ${flags:runtime.build_flags} + -DUSE_ESP32_VARIANT_ESP32S2 + [env:esp32s2-idf-tidy] extends = common:esp32-idf board = esp32-s2-kaluga-1 @@ -337,6 +371,15 @@ build_flags = ${flags:runtime.build_flags} -DUSE_ESP32_VARIANT_ESP32S3 +[env:esp32s3-idf-5_3] +extends = common:esp32-idf-5_3 +board = esp32-s3-devkitc-1 +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s3-idf +build_flags = + ${common:esp32-idf.build_flags} + ${flags:runtime.build_flags} + -DUSE_ESP32_VARIANT_ESP32S3 + [env:esp32s3-idf-tidy] extends = common:esp32-idf board = esp32-s3-devkitc-1 From f28418d0b472e9c641cb4ebcda206f65916fa3bc Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:34:41 +1000 Subject: [PATCH 12/55] [lvgl] Bug fixes (#7370) --- esphome/components/lvgl/__init__.py | 2 ++ esphome/components/lvgl/lv_validation.py | 15 +++++---------- esphome/components/lvgl/schemas.py | 8 +++++++- .../components/lvgl/widgets/buttonmatrix.py | 6 +++++- esphome/components/lvgl/widgets/checkbox.py | 11 ++++++++--- esphome/components/lvgl/widgets/tileview.py | 10 ++-------- tests/components/lvgl/lvgl-package.yaml | 18 ++++++++++++++++-- 7 files changed, 45 insertions(+), 25 deletions(-) diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index cab6462d1a..a4ca9d56f3 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -327,6 +327,8 @@ CONFIG_SCHEMA = ( { cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments, cv.Optional(df.CONF_GRID_CELL_Y_ALIGN): grid_alignments, + cv.Optional(df.CONF_PAD_ROW): lvalid.pixels, + cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels, } ) ), diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index a2be4a2abe..d8af9f7aa9 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -52,9 +52,7 @@ opacity = LValidator(opacity_validator, uint32, retmapper=literal) def color(value): if value == SCHEMA_EXTRACT: return ["hex color value", "color ID"] - if isinstance(value, int): - return value - return cv.use_id(ColorStruct)(value) + return cv.Any(cv.int_, cv.use_id(ColorStruct))(value) def color_retmapper(value): @@ -82,10 +80,10 @@ def pixels_or_percent_validator(value): """A length in one axis - either a number (pixels) or a percentage""" if value == SCHEMA_EXTRACT: return ["pixels", "..%"] + value = cv.Any(cv.int_, cv.percentage)(value) if isinstance(value, int): - return cv.int_(value) - # Will throw an exception if not a percentage. - return f"lv_pct({int(cv.percentage(value) * 100)})" + return value + return f"lv_pct({int(value * 100)})" pixels_or_percent = LValidator(pixels_or_percent_validator, uint32, retmapper=literal) @@ -116,10 +114,7 @@ def size_validator(value): if value.upper() == "SIZE_CONTENT": return "LV_SIZE_CONTENT" raise cv.Invalid("must be 'size_content', a percentage or an integer (pixels)") - if isinstance(value, int): - return cv.int_(value) - # Will throw an exception if not a percentage. - return f"lv_pct({int(cv.percentage(value) * 100)})" + return pixels_or_percent_validator(value) size = LValidator(size_validator, uint32, retmapper=literal) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 548ebda6dc..9ff0fec5bc 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -359,7 +359,13 @@ LVGL_SCHEMA = cv.Schema( } ) -ALL_STYLES = {**STYLE_PROPS, **GRID_CELL_SCHEMA, **FLEX_OBJ_SCHEMA} +ALL_STYLES = { + **STYLE_PROPS, + **GRID_CELL_SCHEMA, + **FLEX_OBJ_SCHEMA, + cv.Optional(df.CONF_PAD_ROW): lvalid.pixels, + cv.Optional(df.CONF_PAD_COLUMN): lvalid.pixels, +} def container_validator(schema, widget_type: WidgetType): diff --git a/esphome/components/lvgl/widgets/buttonmatrix.py b/esphome/components/lvgl/widgets/buttonmatrix.py index e61c5e3477..c65bb4b354 100644 --- a/esphome/components/lvgl/widgets/buttonmatrix.py +++ b/esphome/components/lvgl/widgets/buttonmatrix.py @@ -13,11 +13,13 @@ from ..defines import ( CONF_KEY_CODE, CONF_MAIN, CONF_ONE_CHECKED, + CONF_PAD_COLUMN, + CONF_PAD_ROW, CONF_ROWS, CONF_SELECTED, ) from ..helpers import lvgl_components_required -from ..lv_validation import key_code, lv_bool +from ..lv_validation import key_code, lv_bool, pixels from ..lvcode import lv, lv_add, lv_expr from ..schemas import automation_schema from ..types import ( @@ -57,6 +59,8 @@ BUTTONMATRIX_BUTTON_SCHEMA = cv.Schema( BUTTONMATRIX_SCHEMA = cv.Schema( { cv.Optional(CONF_ONE_CHECKED, default=False): lv_bool, + cv.Optional(CONF_PAD_ROW): pixels, + cv.Optional(CONF_PAD_COLUMN): pixels, cv.GenerateID(CONF_BUTTON_TEXT_LIST_ID): cv.declare_id(char_ptr), cv.Required(CONF_ROWS): cv.ensure_list( cv.Schema( diff --git a/esphome/components/lvgl/widgets/checkbox.py b/esphome/components/lvgl/widgets/checkbox.py index 79c60a8669..75f4142eb1 100644 --- a/esphome/components/lvgl/widgets/checkbox.py +++ b/esphome/components/lvgl/widgets/checkbox.py @@ -1,7 +1,8 @@ +from esphome.config_validation import Optional from esphome.const import CONF_TEXT -from ..defines import CONF_INDICATOR, CONF_MAIN -from ..lv_validation import lv_text +from ..defines import CONF_INDICATOR, CONF_MAIN, CONF_PAD_COLUMN +from ..lv_validation import lv_text, pixels from ..lvcode import lv from ..schemas import TEXT_SCHEMA from ..types import LvBoolean @@ -16,7 +17,11 @@ class CheckboxType(WidgetType): CONF_CHECKBOX, LvBoolean("lv_checkbox_t"), (CONF_MAIN, CONF_INDICATOR), - TEXT_SCHEMA, + TEXT_SCHEMA.extend( + { + Optional(CONF_PAD_COLUMN): pixels, + } + ), ) async def to_code(self, w: Widget, config): diff --git a/esphome/components/lvgl/widgets/tileview.py b/esphome/components/lvgl/widgets/tileview.py index 9a426c7daf..05259fbd3c 100644 --- a/esphome/components/lvgl/widgets/tileview.py +++ b/esphome/components/lvgl/widgets/tileview.py @@ -1,7 +1,7 @@ from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ON_VALUE, CONF_ROW, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_ROW from ..automation import action_to_code from ..defines import ( @@ -29,6 +29,7 @@ lv_tileview_t = LvType( "lv_tileview_t", largs=[(lv_obj_t_ptr, "tile")], lvalue=lambda w: w.get_property("tile_act"), + has_on_value=True, ) tile_spec = WidgetType("lv_tileview_tile_t", lv_tile_t, (CONF_MAIN,), {}) @@ -46,13 +47,6 @@ TILEVIEW_SCHEMA = cv.Schema( }, ) ), - cv.Optional(CONF_ON_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - automation.Trigger.template(lv_obj_t_ptr) - ) - } - ), } ) diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 737d8703b0..0feb6d6ce6 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -337,11 +337,25 @@ lvgl: - tileview: id: tileview_id scrollbar_mode: active + on_value: + then: + - if: + condition: + lambda: return tile == id(tile_1); + then: + - logger.log: "tile 1 is now showing" tiles: - - id: page_1 + - id: tile_1 row: 0 column: 0 - dir: HOR + dir: ALL + widgets: + - obj: + bg_color: 0x000000 + - id: tile_2 + row: 1 + column: 0 + dir: [VER, HOR] widgets: - obj: bg_color: 0x000000 From c09df3c05d4012da3b815ddccc5149a714094274 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 30 Aug 2024 06:16:16 +1000 Subject: [PATCH 13/55] [bytebuffer] Use existing bit_cast operations. (#7374) --- esphome/core/bytebuffer.cpp | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/esphome/core/bytebuffer.cpp b/esphome/core/bytebuffer.cpp index 65525ecfcf..9dd32bf87a 100644 --- a/esphome/core/bytebuffer.cpp +++ b/esphome/core/bytebuffer.cpp @@ -1,6 +1,9 @@ #include "bytebuffer.h" #include -#include +#include "esphome/core/helpers.h" + +#include +#include namespace esphome { @@ -110,18 +113,13 @@ uint32_t ByteBuffer::get_int24() { } float ByteBuffer::get_float() { assert(this->get_remaining() >= sizeof(float)); - auto ui_value = this->get_uint32(); - float value; - memcpy(&value, &ui_value, sizeof(float)); - return value; + return bit_cast(this->get_uint32()); } double ByteBuffer::get_double() { assert(this->get_remaining() >= sizeof(double)); - auto ui_value = this->get_uint64(); - double value; - memcpy(&value, &ui_value, sizeof(double)); - return value; + return bit_cast(this->get_uint64()); } + std::vector ByteBuffer::get_vector(size_t length) { assert(this->get_remaining() >= length); auto start = this->data_.begin() + this->position_; @@ -154,16 +152,12 @@ void ByteBuffer::put_uint(uint64_t value, size_t length) { void ByteBuffer::put_float(float value) { static_assert(sizeof(float) == sizeof(uint32_t), "Float sizes other than 32 bit not supported"); assert(this->get_remaining() >= sizeof(float)); - uint32_t ui_value; - memcpy(&ui_value, &value, sizeof(float)); // this work-around required to silence compiler warnings - this->put_uint32(ui_value); + this->put_uint32(bit_cast(value)); } void ByteBuffer::put_double(double value) { static_assert(sizeof(double) == sizeof(uint64_t), "Double sizes other than 64 bit not supported"); assert(this->get_remaining() >= sizeof(double)); - uint64_t ui_value; - memcpy(&ui_value, &value, sizeof(double)); - this->put_uint64(ui_value); + this->put_uint64(bit_cast(value)); } void ByteBuffer::put_vector(const std::vector &value) { assert(this->get_remaining() >= value.size()); From bb6693a2552f94b1b375a4e5d195e51e27523751 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 08:17:28 +1200 Subject: [PATCH 14/55] Bump actions/setup-python from 5.1.0 to 5.2.0 (#7375) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-api-proto.yml | 2 +- .github/workflows/ci-docker.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/sync-device-classes.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-api-proto.yml b/.github/workflows/ci-api-proto.yml index ee08a0246d..8112c4e0ff 100644 --- a/.github/workflows/ci-api-proto.yml +++ b/.github/workflows/ci-api-proto.yml @@ -23,7 +23,7 @@ jobs: - name: Checkout uses: actions/checkout@v4.1.7 - name: Set up Python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: "3.11" diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 91c02b0a17..891367d16a 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -42,7 +42,7 @@ jobs: steps: - uses: actions/checkout@v4.1.7 - name: Set up Python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: "3.9" - name: Set up Docker Buildx diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2437dd5b8d..7c4fa65695 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d454076c84..937c7aac90 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,7 @@ jobs: steps: - uses: actions/checkout@v4.1.7 - name: Set up Python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: "3.x" - name: Set up python environment @@ -85,7 +85,7 @@ jobs: steps: - uses: actions/checkout@v4.1.7 - name: Set up Python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: "3.9" diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 89a3627c64..7677425236 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -22,7 +22,7 @@ jobs: path: lib/home-assistant - name: Setup Python - uses: actions/setup-python@v5.1.0 + uses: actions/setup-python@v5.2.0 with: python-version: 3.12 From acb00c9c59bd79847fdab7602e06486511bd6dc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 08:17:34 +1200 Subject: [PATCH 15/55] Bump actions/setup-python from 5.1.1 to 5.2.0 in /.github/actions/restore-python (#7376) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/restore-python/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index d3fe2a89dc..c618a5ca97 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -17,7 +17,7 @@ runs: steps: - name: Set up Python ${{ inputs.python-version }} id: python - uses: actions/setup-python@v5.1.1 + uses: actions/setup-python@v5.2.0 with: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment From 725e50348b240c00c2c287d09d9ba8dec138561d Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 30 Aug 2024 06:20:12 +1000 Subject: [PATCH 16/55] [gt911] Add reset pin config (#7373) --- .../components/gt911/touchscreen/__init__.py | 12 +++++---- .../gt911/touchscreen/gt911_touchscreen.cpp | 17 +++++++++++++ .../gt911/touchscreen/gt911_touchscreen.h | 2 ++ tests/components/gt911/common.yaml | 25 +++++++++++++++++++ tests/components/gt911/test.esp32-ard.yaml | 25 +------------------ tests/components/gt911/test.esp32-c3-ard.yaml | 25 +------------------ tests/components/gt911/test.esp32-c3-idf.yaml | 25 +------------------ tests/components/gt911/test.esp32-idf.yaml | 25 +------------------ tests/components/gt911/test.rp2040-ard.yaml | 25 +------------------ 9 files changed, 56 insertions(+), 125 deletions(-) create mode 100644 tests/components/gt911/common.yaml diff --git a/esphome/components/gt911/touchscreen/__init__.py b/esphome/components/gt911/touchscreen/__init__.py index 9a0d5cc169..6c80ff280f 100644 --- a/esphome/components/gt911/touchscreen/__init__.py +++ b/esphome/components/gt911/touchscreen/__init__.py @@ -1,11 +1,10 @@ -import esphome.codegen as cg -import esphome.config_validation as cv - from esphome import pins +import esphome.codegen as cg from esphome.components import i2c, touchscreen -from esphome.const import CONF_INTERRUPT_PIN, CONF_ID -from .. import gt911_ns +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN +from .. import gt911_ns GT911ButtonListener = gt911_ns.class_("GT911ButtonListener") GT911Touchscreen = gt911_ns.class_( @@ -18,6 +17,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(GT911Touchscreen), cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, } ).extend(i2c.i2c_device_schema(0x5D)) @@ -29,3 +29,5 @@ async def to_code(config): if interrupt_pin := config.get(CONF_INTERRUPT_PIN): cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) + if reset_pin := config.get(CONF_RESET_PIN): + cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin))) diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp index 99dba66c22..84811b818f 100644 --- a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp @@ -26,6 +26,23 @@ static const size_t MAX_BUTTONS = 4; // max number of buttons scanned void GT911Touchscreen::setup() { i2c::ErrorCode err; ESP_LOGCONFIG(TAG, "Setting up GT911 Touchscreen..."); + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(false); + if (this->interrupt_pin_ != nullptr) { + // The interrupt pin is used as an input during reset to select the I2C address. + this->interrupt_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->interrupt_pin_->setup(); + this->interrupt_pin_->digital_write(false); + } + delay(2); + this->reset_pin_->digital_write(true); + delay(50); // NOLINT + if (this->interrupt_pin_ != nullptr) { + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT); + this->interrupt_pin_->setup(); + } + } // check the configuration of the int line. uint8_t data[4]; diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.h b/esphome/components/gt911/touchscreen/gt911_touchscreen.h index a9e1279ed3..17636a2ada 100644 --- a/esphome/components/gt911/touchscreen/gt911_touchscreen.h +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.h @@ -19,12 +19,14 @@ class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice void dump_config() override; void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } + void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } void register_button_listener(GT911ButtonListener *listener) { this->button_listeners_.push_back(listener); } protected: void update_touches() override; InternalGPIOPin *interrupt_pin_{}; + GPIOPin *reset_pin_{}; std::vector button_listeners_; uint8_t button_state_{0xFF}; // last button state. Initial FF guarantees first update. }; diff --git a/tests/components/gt911/common.yaml b/tests/components/gt911/common.yaml new file mode 100644 index 0000000000..7bb88108da --- /dev/null +++ b/tests/components/gt911/common.yaml @@ -0,0 +1,25 @@ +i2c: + - id: i2c_gt911 + scl: 5 + sda: 4 + +display: + - platform: ssd1306_i2c + id: ssd1306_display + model: SSD1306_128X64 + reset_pin: 10 + pages: + - id: page1 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + +touchscreen: + - platform: gt911 + display: ssd1306_display + interrupt_pin: 20 + reset_pin: 21 + +binary_sensor: + - platform: gt911 + id: touch_key_911 + index: 0 diff --git a/tests/components/gt911/test.esp32-ard.yaml b/tests/components/gt911/test.esp32-ard.yaml index a47f7bf260..dade44d145 100644 --- a/tests/components/gt911/test.esp32-ard.yaml +++ b/tests/components/gt911/test.esp32-ard.yaml @@ -1,24 +1 @@ -i2c: - - id: i2c_gt911 - scl: 16 - sda: 17 - -display: - - platform: ssd1306_i2c - id: ssd1306_display - model: SSD1306_128X64 - reset_pin: 13 - pages: - - id: page1 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - -touchscreen: - - platform: gt911 - display: ssd1306_display - interrupt_pin: 14 - -binary_sensor: - - platform: gt911 - id: touch_key_911 - index: 0 +<<: !include common.yaml diff --git a/tests/components/gt911/test.esp32-c3-ard.yaml b/tests/components/gt911/test.esp32-c3-ard.yaml index 43f7ac5902..dade44d145 100644 --- a/tests/components/gt911/test.esp32-c3-ard.yaml +++ b/tests/components/gt911/test.esp32-c3-ard.yaml @@ -1,24 +1 @@ -i2c: - - id: i2c_gt911 - scl: 5 - sda: 4 - -display: - - platform: ssd1306_i2c - id: ssd1306_display - model: SSD1306_128X64 - reset_pin: 3 - pages: - - id: page1 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - -touchscreen: - - platform: gt911 - display: ssd1306_display - interrupt_pin: 6 - -binary_sensor: - - platform: gt911 - id: touch_key_911 - index: 0 +<<: !include common.yaml diff --git a/tests/components/gt911/test.esp32-c3-idf.yaml b/tests/components/gt911/test.esp32-c3-idf.yaml index 43f7ac5902..dade44d145 100644 --- a/tests/components/gt911/test.esp32-c3-idf.yaml +++ b/tests/components/gt911/test.esp32-c3-idf.yaml @@ -1,24 +1 @@ -i2c: - - id: i2c_gt911 - scl: 5 - sda: 4 - -display: - - platform: ssd1306_i2c - id: ssd1306_display - model: SSD1306_128X64 - reset_pin: 3 - pages: - - id: page1 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - -touchscreen: - - platform: gt911 - display: ssd1306_display - interrupt_pin: 6 - -binary_sensor: - - platform: gt911 - id: touch_key_911 - index: 0 +<<: !include common.yaml diff --git a/tests/components/gt911/test.esp32-idf.yaml b/tests/components/gt911/test.esp32-idf.yaml index a47f7bf260..dade44d145 100644 --- a/tests/components/gt911/test.esp32-idf.yaml +++ b/tests/components/gt911/test.esp32-idf.yaml @@ -1,24 +1 @@ -i2c: - - id: i2c_gt911 - scl: 16 - sda: 17 - -display: - - platform: ssd1306_i2c - id: ssd1306_display - model: SSD1306_128X64 - reset_pin: 13 - pages: - - id: page1 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - -touchscreen: - - platform: gt911 - display: ssd1306_display - interrupt_pin: 14 - -binary_sensor: - - platform: gt911 - id: touch_key_911 - index: 0 +<<: !include common.yaml diff --git a/tests/components/gt911/test.rp2040-ard.yaml b/tests/components/gt911/test.rp2040-ard.yaml index 43f7ac5902..dade44d145 100644 --- a/tests/components/gt911/test.rp2040-ard.yaml +++ b/tests/components/gt911/test.rp2040-ard.yaml @@ -1,24 +1 @@ -i2c: - - id: i2c_gt911 - scl: 5 - sda: 4 - -display: - - platform: ssd1306_i2c - id: ssd1306_display - model: SSD1306_128X64 - reset_pin: 3 - pages: - - id: page1 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - -touchscreen: - - platform: gt911 - display: ssd1306_display - interrupt_pin: 6 - -binary_sensor: - - platform: gt911 - id: touch_key_911 - index: 0 +<<: !include common.yaml From d754bdde1b74b39f6d78a40a5fa92b721ce4cd93 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 30 Aug 2024 06:27:35 +1000 Subject: [PATCH 17/55] [st7701s] Add delay feature in init sequences (#7343) --- esphome/components/st7701s/display.py | 88 +++++++++++++------------- esphome/components/st7701s/st7701s.cpp | 17 +++-- esphome/components/st7701s/st7701s.h | 1 + tests/components/st7701s/common.yaml | 7 +- 4 files changed, 61 insertions(+), 52 deletions(-) diff --git a/esphome/components/st7701s/display.py b/esphome/components/st7701s/display.py index 516d770f8b..9310e9d760 100644 --- a/esphome/components/st7701s/display.py +++ b/esphome/components/st7701s/display.py @@ -1,47 +1,39 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins -from esphome.components import ( - spi, - display, -) -from esphome.const import ( - CONF_DC_PIN, - CONF_HSYNC_PIN, - CONF_RESET_PIN, - CONF_DATA_PINS, - CONF_ID, - CONF_DIMENSIONS, - CONF_VSYNC_PIN, - CONF_WIDTH, - CONF_HEIGHT, - CONF_LAMBDA, - CONF_MIRROR_X, - CONF_MIRROR_Y, - CONF_COLOR_ORDER, - CONF_TRANSFORM, - CONF_OFFSET_HEIGHT, - CONF_OFFSET_WIDTH, - CONF_INVERT_COLORS, - CONF_RED, - CONF_GREEN, - CONF_BLUE, - CONF_NUMBER, - CONF_IGNORE_STRAPPING_WARNING, -) - -from esphome.components.esp32 import ( - only_on_variant, - const, -) +import esphome.codegen as cg +from esphome.components import display, spi +from esphome.components.esp32 import const, only_on_variant from esphome.components.rpi_dpi_rgb.display import ( CONF_PCLK_FREQUENCY, CONF_PCLK_INVERTED, ) -from .init_sequences import ( - ST7701S_INITS, - cmd, +import esphome.config_validation as cv +from esphome.const import ( + CONF_BLUE, + CONF_COLOR_ORDER, + CONF_DATA_PINS, + CONF_DC_PIN, + CONF_DIMENSIONS, + CONF_GREEN, + CONF_HEIGHT, + CONF_HSYNC_PIN, + CONF_ID, + CONF_IGNORE_STRAPPING_WARNING, + CONF_INVERT_COLORS, + CONF_LAMBDA, + CONF_MIRROR_X, + CONF_MIRROR_Y, + CONF_NUMBER, + CONF_OFFSET_HEIGHT, + CONF_OFFSET_WIDTH, + CONF_RED, + CONF_RESET_PIN, + CONF_TRANSFORM, + CONF_VSYNC_PIN, + CONF_WIDTH, ) +from esphome.core import TimePeriod + +from .init_sequences import ST7701S_INITS, cmd CONF_INIT_SEQUENCE = "init_sequence" CONF_DE_PIN = "de_pin" @@ -59,6 +51,7 @@ DEPENDENCIES = ["spi", "esp32"] st7701s_ns = cg.esphome_ns.namespace("st7701s") ST7701S = st7701s_ns.class_("ST7701S", display.Display, cg.Component, spi.SPIDevice) ColorOrder = display.display_ns.enum("ColorMode") +ST7701S_DELAY_FLAG = 0xFF COLOR_ORDERS = { "RGB": ColorOrder.COLOR_ORDER_RGB, @@ -93,18 +86,23 @@ def map_sequence(value): """ An initialisation sequence can be selected from one of the pre-defined sequences in init_sequences.py, or can be a literal array of data bytes. - The format is a repeated sequence of [CMD, LEN, ] where is LEN bytes. + The format is a repeated sequence of [CMD, ] where is s a sequence of bytes. The length is inferred + from the length of the sequence and should not be explicit. + A delay can be inserted by specifying "- delay N" where N is in ms """ + if isinstance(value, str) and value.lower().startswith("delay "): + value = value.lower()[6:] + delay = cv.All( + cv.positive_time_period_milliseconds, + cv.Range(TimePeriod(milliseconds=1), TimePeriod(milliseconds=255)), + )(value) + return [delay, ST7701S_DELAY_FLAG] if not isinstance(value, list): value = cv.int_(value) value = cv.one_of(*ST7701S_INITS)(value) return ST7701S_INITS[value] - # value = cv.ensure_list(cv.uint8_t)(value) - data_length = len(value) - if data_length == 0: - raise cv.Invalid("Empty sequence") - value = cmd(*value) - return value + value = cv.Length(min=1, max=254)(value) + return cmd(*value) CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/st7701s/st7701s.cpp b/esphome/components/st7701s/st7701s.cpp index 43d8653709..7f02fe1774 100644 --- a/esphome/components/st7701s/st7701s.cpp +++ b/esphome/components/st7701s/st7701s.cpp @@ -138,11 +138,16 @@ void ST7701S::write_init_sequence_() { for (size_t i = 0; i != this->init_sequence_.size();) { uint8_t cmd = this->init_sequence_[i++]; size_t len = this->init_sequence_[i++]; - this->write_sequence_(cmd, len, &this->init_sequence_[i]); - i += len; - esph_log_v(TAG, "Command %X, %d bytes", cmd, len); - if (cmd == SW_RESET_CMD) - delay(6); + if (len == ST7701S_DELAY_FLAG) { + ESP_LOGV(TAG, "Delay %dms", cmd); + delay(cmd); + } else { + this->write_sequence_(cmd, len, &this->init_sequence_[i]); + i += len; + ESP_LOGV(TAG, "Command %X, %d bytes", cmd, len); + if (cmd == SW_RESET_CMD) + delay(6); + } } // st7701 does not appear to support axis swapping this->write_sequence_(CMD2_BKSEL, sizeof(CMD2_BK0), CMD2_BK0); @@ -153,7 +158,7 @@ void ST7701S::write_init_sequence_() { val |= 0x10; this->write_command_(MADCTL_CMD); this->write_data_(val); - esph_log_d(TAG, "write MADCTL %X", val); + ESP_LOGD(TAG, "write MADCTL %X", val); this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF); this->set_timeout(120, [this] { this->write_command_(SLEEP_OUT); diff --git a/esphome/components/st7701s/st7701s.h b/esphome/components/st7701s/st7701s.h index 2328bca965..80e5b81f4a 100644 --- a/esphome/components/st7701s/st7701s.h +++ b/esphome/components/st7701s/st7701s.h @@ -25,6 +25,7 @@ const uint8_t INVERT_ON = 0x21; const uint8_t DISPLAY_ON = 0x29; const uint8_t CMD2_BKSEL = 0xFF; const uint8_t CMD2_BK0[5] = {0x77, 0x01, 0x00, 0x00, 0x10}; +const uint8_t ST7701S_DELAY_FLAG = 0xFF; class ST7701S : public display::Display, public spi::SPIDevice Date: Fri, 30 Aug 2024 09:20:01 +1000 Subject: [PATCH 18/55] Add now required `invert_colors` option to test files referencing ili9xxx (#7367) --- tests/components/animation/test.esp32-ard.yaml | 1 + tests/components/animation/test.esp32-c3-ard.yaml | 1 + tests/components/animation/test.esp32-c3-idf.yaml | 1 + tests/components/animation/test.esp32-idf.yaml | 1 + tests/components/animation/test.esp8266-ard.yaml | 1 + tests/components/animation/test.rp2040-ard.yaml | 1 + tests/components/cst226/common.yaml | 1 + tests/components/cst816/common.yaml | 1 + tests/components/display/common.yaml | 1 + tests/components/ft63x6/test.esp32-ard.yaml | 1 + tests/components/image/test.esp32-ard.yaml | 1 + tests/components/image/test.esp32-c3-ard.yaml | 1 + tests/components/image/test.esp32-c3-idf.yaml | 1 + tests/components/image/test.esp32-idf.yaml | 1 + tests/components/image/test.esp8266-ard.yaml | 1 + tests/components/image/test.rp2040-ard.yaml | 1 + tests/components/online_image/common-esp32.yaml | 1 + tests/components/online_image/common-esp8266.yaml | 1 + tests/components/qr_code/test.esp32-ard.yaml | 1 + tests/components/qr_code/test.esp32-c3-ard.yaml | 1 + tests/components/qr_code/test.esp32-c3-idf.yaml | 1 + tests/components/qr_code/test.esp32-idf.yaml | 1 + tests/components/qr_code/test.esp8266-ard.yaml | 1 + tests/components/qr_code/test.rp2040-ard.yaml | 1 + tests/components/tt21100/test.esp32-s2-ard.yaml | 1 + tests/components/xpt2046/test.esp32-ard.yaml | 1 + tests/components/xpt2046/test.esp32-c3-ard.yaml | 1 + tests/components/xpt2046/test.esp32-c3-idf.yaml | 1 + tests/components/xpt2046/test.esp32-idf.yaml | 1 + tests/components/xpt2046/test.esp32-s2-ard.yaml | 1 + tests/components/xpt2046/test.esp8266-ard.yaml | 1 + tests/components/xpt2046/test.rp2040-ard.yaml | 1 + 32 files changed, 32 insertions(+) diff --git a/tests/components/animation/test.esp32-ard.yaml b/tests/components/animation/test.esp32-ard.yaml index 5dc132eb2d..af6cd202dd 100644 --- a/tests/components/animation/test.esp32-ard.yaml +++ b/tests/components/animation/test.esp32-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 12 dc_pin: 13 reset_pin: 21 + invert_colors: false # Purposely test that `animation:` does auto-load `image:` # Keep the `image:` undefined. diff --git a/tests/components/animation/test.esp32-c3-ard.yaml b/tests/components/animation/test.esp32-c3-ard.yaml index 9bcfbdb118..10e8ccb47e 100644 --- a/tests/components/animation/test.esp32-c3-ard.yaml +++ b/tests/components/animation/test.esp32-c3-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 8 dc_pin: 9 reset_pin: 10 + invert_colors: false # Purposely test that `animation:` does auto-load `image:` # Keep the `image:` undefined. diff --git a/tests/components/animation/test.esp32-c3-idf.yaml b/tests/components/animation/test.esp32-c3-idf.yaml index 9bcfbdb118..10e8ccb47e 100644 --- a/tests/components/animation/test.esp32-c3-idf.yaml +++ b/tests/components/animation/test.esp32-c3-idf.yaml @@ -11,6 +11,7 @@ display: cs_pin: 8 dc_pin: 9 reset_pin: 10 + invert_colors: false # Purposely test that `animation:` does auto-load `image:` # Keep the `image:` undefined. diff --git a/tests/components/animation/test.esp32-idf.yaml b/tests/components/animation/test.esp32-idf.yaml index 5dc132eb2d..af6cd202dd 100644 --- a/tests/components/animation/test.esp32-idf.yaml +++ b/tests/components/animation/test.esp32-idf.yaml @@ -11,6 +11,7 @@ display: cs_pin: 12 dc_pin: 13 reset_pin: 21 + invert_colors: false # Purposely test that `animation:` does auto-load `image:` # Keep the `image:` undefined. diff --git a/tests/components/animation/test.esp8266-ard.yaml b/tests/components/animation/test.esp8266-ard.yaml index ef0f483a79..ced4996f25 100644 --- a/tests/components/animation/test.esp8266-ard.yaml +++ b/tests/components/animation/test.esp8266-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 5 dc_pin: 15 reset_pin: 16 + invert_colors: false # Purposely test that `animation:` does auto-load `image:` # Keep the `image:` undefined. diff --git a/tests/components/animation/test.rp2040-ard.yaml b/tests/components/animation/test.rp2040-ard.yaml index 6ee29a3347..0e33959cc6 100644 --- a/tests/components/animation/test.rp2040-ard.yaml +++ b/tests/components/animation/test.rp2040-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 20 dc_pin: 21 reset_pin: 22 + invert_colors: false # Purposely test that `animation:` does auto-load `image:` # Keep the `image:` undefined. diff --git a/tests/components/cst226/common.yaml b/tests/components/cst226/common.yaml index 4cbf38ef50..7e1c5dde36 100644 --- a/tests/components/cst226/common.yaml +++ b/tests/components/cst226/common.yaml @@ -12,6 +12,7 @@ display: dc_pin: GPIO4 reset_pin: number: GPIO21 + invert_colors: false i2c: scl: GPIO18 diff --git a/tests/components/cst816/common.yaml b/tests/components/cst816/common.yaml index f8deea6e98..91abbfd4f6 100644 --- a/tests/components/cst816/common.yaml +++ b/tests/components/cst816/common.yaml @@ -26,6 +26,7 @@ display: mirror_x: true mirror_y: true auto_clear_enabled: false + invert_colors: false spi: clk_pin: 14 diff --git a/tests/components/display/common.yaml b/tests/components/display/common.yaml index a22aa76780..1df2665067 100644 --- a/tests/components/display/common.yaml +++ b/tests/components/display/common.yaml @@ -11,6 +11,7 @@ display: cs_pin: 12 dc_pin: 13 reset_pin: 21 + invert_colors: false lambda: |- // Draw an analog clock in the center of the screen int centerX = it.get_width() / 2; diff --git a/tests/components/ft63x6/test.esp32-ard.yaml b/tests/components/ft63x6/test.esp32-ard.yaml index 32d6634dae..5c43cdff45 100644 --- a/tests/components/ft63x6/test.esp32-ard.yaml +++ b/tests/components/ft63x6/test.esp32-ard.yaml @@ -19,6 +19,7 @@ display: mirror_x: true mirror_y: true auto_clear_enabled: false + invert_colors: false touchscreen: - platform: ft63x6 diff --git a/tests/components/image/test.esp32-ard.yaml b/tests/components/image/test.esp32-ard.yaml index ff9adde6b1..34c7914976 100644 --- a/tests/components/image/test.esp32-ard.yaml +++ b/tests/components/image/test.esp32-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 12 dc_pin: 13 reset_pin: 21 + invert_colors: true image: - id: binary_image diff --git a/tests/components/image/test.esp32-c3-ard.yaml b/tests/components/image/test.esp32-c3-ard.yaml index c083a97c94..91ff0a0579 100644 --- a/tests/components/image/test.esp32-c3-ard.yaml +++ b/tests/components/image/test.esp32-c3-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 8 dc_pin: 9 reset_pin: 10 + invert_colors: true image: - id: binary_image diff --git a/tests/components/image/test.esp32-c3-idf.yaml b/tests/components/image/test.esp32-c3-idf.yaml index c083a97c94..91ff0a0579 100644 --- a/tests/components/image/test.esp32-c3-idf.yaml +++ b/tests/components/image/test.esp32-c3-idf.yaml @@ -11,6 +11,7 @@ display: cs_pin: 8 dc_pin: 9 reset_pin: 10 + invert_colors: true image: - id: binary_image diff --git a/tests/components/image/test.esp32-idf.yaml b/tests/components/image/test.esp32-idf.yaml index ff9adde6b1..34c7914976 100644 --- a/tests/components/image/test.esp32-idf.yaml +++ b/tests/components/image/test.esp32-idf.yaml @@ -11,6 +11,7 @@ display: cs_pin: 12 dc_pin: 13 reset_pin: 21 + invert_colors: true image: - id: binary_image diff --git a/tests/components/image/test.esp8266-ard.yaml b/tests/components/image/test.esp8266-ard.yaml index 3632b95485..5a96ed9497 100644 --- a/tests/components/image/test.esp8266-ard.yaml +++ b/tests/components/image/test.esp8266-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 5 dc_pin: 15 reset_pin: 16 + invert_colors: true image: - id: binary_image diff --git a/tests/components/image/test.rp2040-ard.yaml b/tests/components/image/test.rp2040-ard.yaml index b79c8a9195..4c40ca464f 100644 --- a/tests/components/image/test.rp2040-ard.yaml +++ b/tests/components/image/test.rp2040-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 20 dc_pin: 21 reset_pin: 22 + invert_colors: true image: - id: binary_image diff --git a/tests/components/online_image/common-esp32.yaml b/tests/components/online_image/common-esp32.yaml index 8cc50fc3e0..d3a304cdc0 100644 --- a/tests/components/online_image/common-esp32.yaml +++ b/tests/components/online_image/common-esp32.yaml @@ -13,6 +13,7 @@ display: cs_pin: 12 dc_pin: 13 reset_pin: 21 + invert_colors: true lambda: |- it.fill(Color(0, 0, 0)); it.image(0, 0, id(online_rgba_image)); diff --git a/tests/components/online_image/common-esp8266.yaml b/tests/components/online_image/common-esp8266.yaml index 01e3467413..ba15b5025c 100644 --- a/tests/components/online_image/common-esp8266.yaml +++ b/tests/components/online_image/common-esp8266.yaml @@ -13,6 +13,7 @@ display: cs_pin: 15 dc_pin: 3 reset_pin: 1 + invert_colors: true lambda: |- it.fill(Color(0, 0, 0)); it.image(0, 0, id(online_rgba_image)); diff --git a/tests/components/qr_code/test.esp32-ard.yaml b/tests/components/qr_code/test.esp32-ard.yaml index 3e70d3258f..8689d4d73f 100644 --- a/tests/components/qr_code/test.esp32-ard.yaml +++ b/tests/components/qr_code/test.esp32-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 12 dc_pin: 13 reset_pin: 21 + invert_colors: false lambda: |- // Draw a QR code in the center of the screen auto scale = 2; diff --git a/tests/components/qr_code/test.esp32-c3-ard.yaml b/tests/components/qr_code/test.esp32-c3-ard.yaml index 63973b1aa2..3690d2598c 100644 --- a/tests/components/qr_code/test.esp32-c3-ard.yaml +++ b/tests/components/qr_code/test.esp32-c3-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 8 dc_pin: 9 reset_pin: 10 + invert_colors: false lambda: |- // Draw a QR code in the center of the screen auto scale = 2; diff --git a/tests/components/qr_code/test.esp32-c3-idf.yaml b/tests/components/qr_code/test.esp32-c3-idf.yaml index 63973b1aa2..3690d2598c 100644 --- a/tests/components/qr_code/test.esp32-c3-idf.yaml +++ b/tests/components/qr_code/test.esp32-c3-idf.yaml @@ -11,6 +11,7 @@ display: cs_pin: 8 dc_pin: 9 reset_pin: 10 + invert_colors: false lambda: |- // Draw a QR code in the center of the screen auto scale = 2; diff --git a/tests/components/qr_code/test.esp32-idf.yaml b/tests/components/qr_code/test.esp32-idf.yaml index 3e70d3258f..8689d4d73f 100644 --- a/tests/components/qr_code/test.esp32-idf.yaml +++ b/tests/components/qr_code/test.esp32-idf.yaml @@ -11,6 +11,7 @@ display: cs_pin: 12 dc_pin: 13 reset_pin: 21 + invert_colors: false lambda: |- // Draw a QR code in the center of the screen auto scale = 2; diff --git a/tests/components/qr_code/test.esp8266-ard.yaml b/tests/components/qr_code/test.esp8266-ard.yaml index 3c304d7575..02dc183440 100644 --- a/tests/components/qr_code/test.esp8266-ard.yaml +++ b/tests/components/qr_code/test.esp8266-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 5 dc_pin: 15 reset_pin: 16 + invert_colors: false lambda: |- // Draw a QR code in the center of the screen auto scale = 2; diff --git a/tests/components/qr_code/test.rp2040-ard.yaml b/tests/components/qr_code/test.rp2040-ard.yaml index 94cb772ba3..0d86f8d213 100644 --- a/tests/components/qr_code/test.rp2040-ard.yaml +++ b/tests/components/qr_code/test.rp2040-ard.yaml @@ -11,6 +11,7 @@ display: cs_pin: 20 dc_pin: 21 reset_pin: 22 + invert_colors: false lambda: |- // Draw a QR code in the center of the screen auto scale = 2; diff --git a/tests/components/tt21100/test.esp32-s2-ard.yaml b/tests/components/tt21100/test.esp32-s2-ard.yaml index 7ebabcb130..86b9e7530d 100644 --- a/tests/components/tt21100/test.esp32-s2-ard.yaml +++ b/tests/components/tt21100/test.esp32-s2-ard.yaml @@ -18,6 +18,7 @@ display: data_rate: 40MHz dimensions: 320x240 update_interval: never + invert_colors: false transform: mirror_y: false mirror_x: false diff --git a/tests/components/xpt2046/test.esp32-ard.yaml b/tests/components/xpt2046/test.esp32-ard.yaml index bb166866f4..f15d1f9b41 100644 --- a/tests/components/xpt2046/test.esp32-ard.yaml +++ b/tests/components/xpt2046/test.esp32-ard.yaml @@ -12,6 +12,7 @@ display: cs_pin: 13 dc_pin: 14 reset_pin: 21 + invert_colors: false lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/xpt2046/test.esp32-c3-ard.yaml b/tests/components/xpt2046/test.esp32-c3-ard.yaml index f3a2cf9aae..ef4daa800d 100644 --- a/tests/components/xpt2046/test.esp32-c3-ard.yaml +++ b/tests/components/xpt2046/test.esp32-c3-ard.yaml @@ -12,6 +12,7 @@ display: cs_pin: 8 dc_pin: 9 reset_pin: 10 + invert_colors: false lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/xpt2046/test.esp32-c3-idf.yaml b/tests/components/xpt2046/test.esp32-c3-idf.yaml index f3a2cf9aae..ef4daa800d 100644 --- a/tests/components/xpt2046/test.esp32-c3-idf.yaml +++ b/tests/components/xpt2046/test.esp32-c3-idf.yaml @@ -12,6 +12,7 @@ display: cs_pin: 8 dc_pin: 9 reset_pin: 10 + invert_colors: false lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/xpt2046/test.esp32-idf.yaml b/tests/components/xpt2046/test.esp32-idf.yaml index bb166866f4..f15d1f9b41 100644 --- a/tests/components/xpt2046/test.esp32-idf.yaml +++ b/tests/components/xpt2046/test.esp32-idf.yaml @@ -12,6 +12,7 @@ display: cs_pin: 13 dc_pin: 14 reset_pin: 21 + invert_colors: false lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/xpt2046/test.esp32-s2-ard.yaml b/tests/components/xpt2046/test.esp32-s2-ard.yaml index 6232ca957b..df2a99b4f5 100644 --- a/tests/components/xpt2046/test.esp32-s2-ard.yaml +++ b/tests/components/xpt2046/test.esp32-s2-ard.yaml @@ -14,6 +14,7 @@ display: data_rate: 40MHz dimensions: 320x240 update_interval: never + invert_colors: false transform: mirror_y: false mirror_x: false diff --git a/tests/components/xpt2046/test.esp8266-ard.yaml b/tests/components/xpt2046/test.esp8266-ard.yaml index a917290e8e..0daa25ad60 100644 --- a/tests/components/xpt2046/test.esp8266-ard.yaml +++ b/tests/components/xpt2046/test.esp8266-ard.yaml @@ -12,6 +12,7 @@ display: cs_pin: 15 dc_pin: 4 reset_pin: 5 + invert_colors: false lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/xpt2046/test.rp2040-ard.yaml b/tests/components/xpt2046/test.rp2040-ard.yaml index a7a49309ac..8afc45d04d 100644 --- a/tests/components/xpt2046/test.rp2040-ard.yaml +++ b/tests/components/xpt2046/test.rp2040-ard.yaml @@ -12,6 +12,7 @@ display: cs_pin: 8 dc_pin: 9 reset_pin: 10 + invert_colors: false lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); From 69f98e0f87eacdad0be79fc33287e869c37587ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20Kry=C5=84ski?= Date: Fri, 30 Aug 2024 01:43:47 +0200 Subject: [PATCH 19/55] esp32_can: make queue lengths configurable (#7361) --- esphome/components/esp32_can/canbus.py | 25 ++++++++++++++++------ esphome/components/esp32_can/esp32_can.cpp | 7 ++++++ esphome/components/esp32_can/esp32_can.h | 4 ++++ esphome/const.py | 2 ++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index f4ba032009..37bdfa3962 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -1,18 +1,23 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import canbus -from esphome.const import CONF_ID, CONF_RX_PIN, CONF_TX_PIN -from esphome.components.canbus import CanbusComponent, CanSpeed, CONF_BIT_RATE - +from esphome.components.canbus import CONF_BIT_RATE, CanbusComponent, CanSpeed from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32.const import ( VARIANT_ESP32, - VARIANT_ESP32S2, - VARIANT_ESP32S3, VARIANT_ESP32C3, VARIANT_ESP32C6, VARIANT_ESP32H2, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_RX_PIN, + CONF_RX_QUEUE_LEN, + CONF_TX_PIN, + CONF_TX_QUEUE_LEN, ) CODEOWNERS = ["@Sympatron"] @@ -77,6 +82,8 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend( cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate, cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number, cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_RX_QUEUE_LEN): cv.uint32_t, + cv.Optional(CONF_TX_QUEUE_LEN): cv.uint32_t, } ) @@ -87,3 +94,7 @@ async def to_code(config): cg.add(var.set_rx(config[CONF_RX_PIN])) cg.add(var.set_tx(config[CONF_TX_PIN])) + if (rx_queue_len := config.get(CONF_RX_QUEUE_LEN)) is not None: + cg.add(var.set_rx_queue_len(rx_queue_len)) + if (tx_queue_len := config.get(CONF_TX_QUEUE_LEN)) is not None: + cg.add(var.set_tx_queue_len(tx_queue_len)) diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index 214b72e864..5a45859b1f 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -69,6 +69,13 @@ static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config bool ESP32Can::setup_internal() { twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL); + if (this->tx_queue_len_.has_value()) { + g_config.tx_queue_len = this->tx_queue_len_.value(); + } + if (this->rx_queue_len_.has_value()) { + g_config.rx_queue_len = this->rx_queue_len_.value(); + } + twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); twai_timing_config_t t_config; diff --git a/esphome/components/esp32_can/esp32_can.h b/esphome/components/esp32_can/esp32_can.h index a428834f65..b3086f9a48 100644 --- a/esphome/components/esp32_can/esp32_can.h +++ b/esphome/components/esp32_can/esp32_can.h @@ -12,6 +12,8 @@ class ESP32Can : public canbus::Canbus { public: void set_rx(int rx) { rx_ = rx; } void set_tx(int tx) { tx_ = tx; } + void set_tx_queue_len(uint32_t tx_queue_len) { this->tx_queue_len_ = tx_queue_len; } + void set_rx_queue_len(uint32_t rx_queue_len) { this->rx_queue_len_ = rx_queue_len; } ESP32Can(){}; protected: @@ -21,6 +23,8 @@ class ESP32Can : public canbus::Canbus { int rx_{-1}; int tx_{-1}; + optional tx_queue_len_{}; + optional rx_queue_len_{}; }; } // namespace esp32_can diff --git a/esphome/const.py b/esphome/const.py index 6e29667887..95773630d0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -730,6 +730,7 @@ CONF_RW_PIN = "rw_pin" CONF_RX_BUFFER_SIZE = "rx_buffer_size" CONF_RX_ONLY = "rx_only" CONF_RX_PIN = "rx_pin" +CONF_RX_QUEUE_LEN = "rx_queue_len" CONF_SAFE_MODE = "safe_mode" CONF_SAMPLE_RATE = "sample_rate" CONF_SAMSUNG = "samsung" @@ -881,6 +882,7 @@ CONF_TVOC = "tvoc" CONF_TX_BUFFER_SIZE = "tx_buffer_size" CONF_TX_PIN = "tx_pin" CONF_TX_POWER = "tx_power" +CONF_TX_QUEUE_LEN = "tx_queue_len" CONF_TYPE = "type" CONF_TYPE_ID = "type_id" CONF_UART_ID = "uart_id" From f8e8bd2c24666fe1efaf60a44ea46dfae3093341 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Fri, 30 Aug 2024 02:03:44 +0200 Subject: [PATCH 20/55] [code-quality] fix clang-tidy web_server and web_server_base (#7286) --- esphome/components/web_server/__init__.py | 26 +++++++++---------- .../components/web_server/list_entities.cpp | 2 ++ esphome/components/web_server/list_entities.h | 4 ++- esphome/components/web_server/web_server.cpp | 3 ++- esphome/components/web_server/web_server.h | 2 ++ .../web_server_base/web_server_base.cpp | 2 ++ .../web_server_base/web_server_base.h | 4 ++- 7 files changed, 27 insertions(+), 16 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 232ab40d10..02074dcf11 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -1,35 +1,36 @@ from __future__ import annotations import gzip + import esphome.codegen as cg -import esphome.config_validation as cv -import esphome.final_validate as fv from esphome.components import web_server_base from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID +import esphome.config_validation as cv from esphome.const import ( + CONF_AUTH, CONF_CSS_INCLUDE, CONF_CSS_URL, + CONF_ENABLE_PRIVATE_NETWORK_ACCESS, CONF_ID, + CONF_INCLUDE_INTERNAL, CONF_JS_INCLUDE, CONF_JS_URL, - CONF_ENABLE_PRIVATE_NETWORK_ACCESS, - CONF_PORT, - CONF_AUTH, - CONF_USERNAME, - CONF_PASSWORD, - CONF_INCLUDE_INTERNAL, - CONF_OTA, - CONF_LOG, - CONF_VERSION, CONF_LOCAL, + CONF_LOG, + CONF_OTA, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, + CONF_VERSION, CONF_WEB_SERVER_ID, CONF_WEB_SERVER_SORTING_WEIGHT, + PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, - PLATFORM_BK72XX, PLATFORM_RTL87XX, ) from esphome.core import CORE, coroutine_with_priority +import esphome.final_validate as fv AUTO_LOAD = ["json", "web_server_base"] @@ -208,7 +209,6 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], paren) await cg.register_component(var, config) - cg.add_define("USE_WEBSERVER") version = config[CONF_VERSION] cg.add(paren.set_port(config[CONF_PORT])) diff --git a/esphome/components/web_server/list_entities.cpp b/esphome/components/web_server/list_entities.cpp index 332f358352..a02f84c34b 100644 --- a/esphome/components/web_server/list_entities.cpp +++ b/esphome/components/web_server/list_entities.cpp @@ -1,4 +1,5 @@ #include "list_entities.h" +#ifdef USE_WEBSERVER #include "esphome/core/application.h" #include "esphome/core/log.h" #include "esphome/core/util.h" @@ -188,3 +189,4 @@ bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { } // namespace web_server } // namespace esphome +#endif diff --git a/esphome/components/web_server/list_entities.h b/esphome/components/web_server/list_entities.h index 5ff6ec0412..53e5bc3355 100644 --- a/esphome/components/web_server/list_entities.h +++ b/esphome/components/web_server/list_entities.h @@ -1,8 +1,9 @@ #pragma once +#include "esphome/core/defines.h" +#ifdef USE_WEBSERVER #include "esphome/core/component.h" #include "esphome/core/component_iterator.h" -#include "esphome/core/defines.h" namespace esphome { namespace web_server { @@ -78,3 +79,4 @@ class ListEntitiesIterator : public ComponentIterator { } // namespace web_server } // namespace esphome +#endif diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 6fb04f558a..1bb7c6c249 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1,5 +1,5 @@ #include "web_server.h" - +#ifdef USE_WEBSERVER #include "esphome/components/json/json_util.h" #include "esphome/components/network/util.h" #include "esphome/core/application.h" @@ -1659,3 +1659,4 @@ void WebServer::schedule_(std::function &&f) { } // namespace web_server } // namespace esphome +#endif diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index d4ab592b7b..3195fa7109 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -3,6 +3,7 @@ #include "list_entities.h" #include "esphome/components/web_server_base/web_server_base.h" +#ifdef USE_WEBSERVER #include "esphome/core/component.h" #include "esphome/core/controller.h" #include "esphome/core/entity_base.h" @@ -366,3 +367,4 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { } // namespace web_server } // namespace esphome +#endif diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index f90c7e56a3..7c09022f27 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -1,4 +1,5 @@ #include "web_server_base.h" +#ifdef USE_NETWORK #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" @@ -121,3 +122,4 @@ float WebServerBase::get_setup_priority() const { } // namespace web_server_base } // namespace esphome +#endif diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 2282d55ec1..f876d163bc 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -1,5 +1,6 @@ #pragma once - +#include "esphome/core/defines.h" +#ifdef USE_NETWORK #include #include #include @@ -145,3 +146,4 @@ class OTARequestHandler : public AsyncWebHandler { } // namespace web_server_base } // namespace esphome +#endif From a5d46ae9e553d27e7f66dc3bd87c64dc86b2cf36 Mon Sep 17 00:00:00 2001 From: Trevor Schirmer <24777085+TrevorSchirmer@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:36:32 -0400 Subject: [PATCH 21/55] Update MiCS Values (#7173) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/mics_4514/mics_4514.cpp | 62 +++++++++------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/esphome/components/mics_4514/mics_4514.cpp b/esphome/components/mics_4514/mics_4514.cpp index a14d7f2f80..ed2fc6c826 100644 --- a/esphome/components/mics_4514/mics_4514.cpp +++ b/esphome/components/mics_4514/mics_4514.cpp @@ -70,72 +70,62 @@ void MICS4514Component::update() { if (this->carbon_monoxide_sensor_ != nullptr) { float co = 0.0f; - if (red_f <= 0.425f) { - co = (0.425f - red_f) / 0.000405f; - if (co < 1.0f) - co = 0.0f; - if (co > 1000.0f) - co = 1000.0f; + if (red_f > 3.4f) { + co = 0.0; + } else if (red_f < 0.01) { + co = 1000.0; + } else { + co = 4.2 / pow(red_f, 1.2); } this->carbon_monoxide_sensor_->publish_state(co); } if (this->nitrogen_dioxide_sensor_ != nullptr) { float nitrogendioxide = 0.0f; - if (ox_f >= 1.1f) { - nitrogendioxide = (ox_f - 0.045f) / 6.13f; - if (nitrogendioxide < 0.1f) - nitrogendioxide = 0.0f; - if (nitrogendioxide > 10.0f) - nitrogendioxide = 10.0f; + if (ox_f < 0.3f) { + nitrogendioxide = 0.0; + } else { + nitrogendioxide = 0.164 * pow(ox_f, 0.975); } this->nitrogen_dioxide_sensor_->publish_state(nitrogendioxide); } if (this->methane_sensor_ != nullptr) { float methane = 0.0f; - if (red_f <= 0.786f) { - methane = (0.786f - red_f) / 0.000023f; - if (methane < 1000.0f) - methane = 0.0f; - if (methane > 25000.0f) - methane = 25000.0f; + if (red_f > 0.9f || red_f < 0.5) { // outside the range->unlikely + methane = 0.0; + } else { + methane = 630 / pow(red_f, 4.4); } this->methane_sensor_->publish_state(methane); } if (this->ethanol_sensor_ != nullptr) { float ethanol = 0.0f; - if (red_f <= 0.306f) { - ethanol = (0.306f - red_f) / 0.00057f; - if (ethanol < 10.0f) - ethanol = 0.0f; - if (ethanol > 500.0f) - ethanol = 500.0f; + if (red_f > 1.0f || red_f < 0.02) { // outside the range->unlikely + ethanol = 0.0; + } else { + ethanol = 1.52 / pow(red_f, 1.55); } this->ethanol_sensor_->publish_state(ethanol); } if (this->hydrogen_sensor_ != nullptr) { float hydrogen = 0.0f; - if (red_f <= 0.279f) { - hydrogen = (0.279f - red_f) / 0.00026f; - if (hydrogen < 1.0f) - hydrogen = 0.0f; - if (hydrogen > 1000.0f) - hydrogen = 1000.0f; + if (red_f > 0.9f || red_f < 0.02) { // outside the range->unlikely + hydrogen = 0.0; + } else { + hydrogen = 0.85 / pow(red_f, 1.75); } this->hydrogen_sensor_->publish_state(hydrogen); } if (this->ammonia_sensor_ != nullptr) { float ammonia = 0.0f; - if (red_f <= 0.8f) { - ammonia = (0.8f - red_f) / 0.0015f; - if (ammonia < 1.0f) - ammonia = 0.0f; - if (ammonia > 500.0f) - ammonia = 500.0f; + if (red_f > 0.98f || red_f < 0.2532) { // outside the ammonia range->unlikely + ammonia = 0.0; + } else { + ammonia = 0.9 / pow(red_f, 4.6); } this->ammonia_sensor_->publish_state(ammonia); } From 721b532d71b4ca82c811e3db3b3e189e134704d8 Mon Sep 17 00:00:00 2001 From: Piotr Szulc Date: Fri, 30 Aug 2024 02:53:34 +0200 Subject: [PATCH 22/55] Tuya Number: allow restoring value of hidden datapoints (#7346) --- esphome/components/tuya/number/__init__.py | 3 ++ .../components/tuya/number/tuya_number.cpp | 45 +++++++++++++++++-- esphome/components/tuya/number/tuya_number.h | 9 +++- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/esphome/components/tuya/number/__init__.py b/esphome/components/tuya/number/__init__.py index 25be6329ab..c00ea08d23 100644 --- a/esphome/components/tuya/number/__init__.py +++ b/esphome/components/tuya/number/__init__.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_MULTIPLY, CONF_STEP, CONF_INITIAL_VALUE, + CONF_RESTORE_VALUE, ) from .. import tuya_ns, CONF_TUYA_ID, Tuya, TuyaDatapointType @@ -58,6 +59,7 @@ CONFIG_SCHEMA = cv.All( DATAPOINT_TYPES, lower=True ), cv.Optional(CONF_INITIAL_VALUE): cv.float_, + cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, } ) ), @@ -90,3 +92,4 @@ async def to_code(config): hidden_init_value := hidden_config.get(CONF_INITIAL_VALUE, None) ) is not None: cg.add(var.set_datapoint_initial_value(hidden_init_value)) + cg.add(var.set_restore_value(hidden_config[CONF_RESTORE_VALUE])) diff --git a/esphome/components/tuya/number/tuya_number.cpp b/esphome/components/tuya/number/tuya_number.cpp index 7eeb08fde2..68a7f8f2a7 100644 --- a/esphome/components/tuya/number/tuya_number.cpp +++ b/esphome/components/tuya/number/tuya_number.cpp @@ -7,14 +7,28 @@ namespace tuya { static const char *const TAG = "tuya.number"; void TuyaNumber::setup() { + if (this->restore_value_) { + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); + } + this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) { if (datapoint.type == TuyaDatapointType::INTEGER) { ESP_LOGV(TAG, "MCU reported number %u is: %d", datapoint.id, datapoint.value_int); - this->publish_state(datapoint.value_int / multiply_by_); + float value = datapoint.value_int / multiply_by_; + this->publish_state(value); + if (this->restore_value_) + this->pref_.save(&value); } else if (datapoint.type == TuyaDatapointType::ENUM) { ESP_LOGV(TAG, "MCU reported number %u is: %u", datapoint.id, datapoint.value_enum); - this->publish_state(datapoint.value_enum); + float value = datapoint.value_enum; + this->publish_state(value); + if (this->restore_value_) + this->pref_.save(&value); + } else { + ESP_LOGW(TAG, "Reported type (%d) is not a number!", static_cast(datapoint.type)); + return; } + if ((this->type_) && (this->type_ != datapoint.type)) { ESP_LOGW(TAG, "Reported type (%d) different than previously set (%d)!", static_cast(datapoint.type), static_cast(*this->type_)); @@ -23,8 +37,26 @@ void TuyaNumber::setup() { }); this->parent_->add_on_initialized_callback([this] { - if ((this->initial_value_) && (this->type_)) { - this->control(*this->initial_value_); + if (this->type_) { + float value; + if (!this->restore_value_) { + if (this->initial_value_) { + value = *this->initial_value_; + } else { + return; + } + } else { + if (!this->pref_.load(&value)) { + if (this->initial_value_) { + value = *this->initial_value_; + } else { + value = this->traits.get_min_value(); + ESP_LOGW(TAG, "Failed to restore and there is no initial value defined. Setting min_value (%f)", value); + } + } + } + + this->control(value); } }); } @@ -38,6 +70,9 @@ void TuyaNumber::control(float value) { this->parent_->set_enum_datapoint_value(this->number_id_, value); } this->publish_state(value); + + if (this->restore_value_) + this->pref_.save(&value); } void TuyaNumber::dump_config() { @@ -52,6 +87,8 @@ void TuyaNumber::dump_config() { if (this->initial_value_) { ESP_LOGCONFIG(TAG, " Initial Value: %f", *this->initial_value_); } + + ESP_LOGCONFIG(TAG, " Restore Value: %s", YESNO(this->restore_value_)); } } // namespace tuya diff --git a/esphome/components/tuya/number/tuya_number.h b/esphome/components/tuya/number/tuya_number.h index 545584128e..53137d6f66 100644 --- a/esphome/components/tuya/number/tuya_number.h +++ b/esphome/components/tuya/number/tuya_number.h @@ -1,9 +1,10 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/components/tuya/tuya.h" #include "esphome/components/number/number.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/core/component.h" #include "esphome/core/optional.h" +#include "esphome/core/preferences.h" namespace esphome { namespace tuya { @@ -16,6 +17,7 @@ class TuyaNumber : public number::Number, public Component { void set_write_multiply(float factor) { multiply_by_ = factor; } void set_datapoint_type(TuyaDatapointType type) { type_ = type; } void set_datapoint_initial_value(float value) { this->initial_value_ = value; } + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } @@ -27,6 +29,9 @@ class TuyaNumber : public number::Number, public Component { float multiply_by_{1.0}; optional type_{}; optional initial_value_{}; + bool restore_value_{false}; + + ESPPreferenceObject pref_; }; } // namespace tuya From ba6963cf72811cd75d4cd5510d1b81378c049ded Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:59:55 +1000 Subject: [PATCH 23/55] [udp] Implement UDP sensor broadcast (#6865) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: clydebarrow <366188+clydebarrow@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/udp/__init__.py | 158 +++++ esphome/components/udp/binary_sensor.py | 27 + esphome/components/udp/sensor.py | 27 + esphome/components/udp/udp_component.cpp | 616 ++++++++++++++++++++ esphome/components/udp/udp_component.h | 158 +++++ tests/components/udp/common.yaml | 35 ++ tests/components/udp/test.bk72xx-ard.yaml | 1 + tests/components/udp/test.esp32-ard.yaml | 1 + tests/components/udp/test.esp32-c3-ard.yaml | 1 + tests/components/udp/test.esp32-c3-idf.yaml | 1 + tests/components/udp/test.esp32-idf.yaml | 1 + tests/components/udp/test.esp8266-ard.yaml | 1 + tests/components/udp/test.host.yaml | 4 + tests/components/udp/test.rp2040-ard.yaml | 1 + 15 files changed, 1033 insertions(+) create mode 100644 esphome/components/udp/__init__.py create mode 100644 esphome/components/udp/binary_sensor.py create mode 100644 esphome/components/udp/sensor.py create mode 100644 esphome/components/udp/udp_component.cpp create mode 100644 esphome/components/udp/udp_component.h create mode 100644 tests/components/udp/common.yaml create mode 100644 tests/components/udp/test.bk72xx-ard.yaml create mode 100644 tests/components/udp/test.esp32-ard.yaml create mode 100644 tests/components/udp/test.esp32-c3-ard.yaml create mode 100644 tests/components/udp/test.esp32-c3-idf.yaml create mode 100644 tests/components/udp/test.esp32-idf.yaml create mode 100644 tests/components/udp/test.esp8266-ard.yaml create mode 100644 tests/components/udp/test.host.yaml create mode 100644 tests/components/udp/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 40511e2f41..807829eafd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -423,6 +423,7 @@ esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/uart/button/* @ssieb +esphome/components/udp/* @clydebarrow esphome/components/ufire_ec/* @pvizeli esphome/components/ufire_ise/* @pvizeli esphome/components/ultrasonic/* @OttoWinter diff --git a/esphome/components/udp/__init__.py b/esphome/components/udp/__init__.py new file mode 100644 index 0000000000..ca15be2a80 --- /dev/null +++ b/esphome/components/udp/__init__.py @@ -0,0 +1,158 @@ +import hashlib + +import esphome.codegen as cg +from esphome.components.api import CONF_ENCRYPTION +from esphome.components.binary_sensor import BinarySensor +from esphome.components.sensor import Sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_BINARY_SENSORS, + CONF_ID, + CONF_INTERNAL, + CONF_KEY, + CONF_NAME, + CONF_PORT, + CONF_SENSORS, +) +from esphome.cpp_generator import MockObjClass + +CODEOWNERS = ["@clydebarrow"] +DEPENDENCIES = ["network"] +AUTO_LOAD = ["socket"] +MULTI_CONF = True + +udp_ns = cg.esphome_ns.namespace("udp") +UDPComponent = udp_ns.class_("UDPComponent", cg.PollingComponent) + +CONF_BROADCAST = "broadcast" +CONF_BROADCAST_ID = "broadcast_id" +CONF_ADDRESSES = "addresses" +CONF_PROVIDER = "provider" +CONF_PROVIDERS = "providers" +CONF_REMOTE_ID = "remote_id" +CONF_UDP_ID = "udp_id" +CONF_PING_PONG_ENABLE = "ping_pong_enable" +CONF_PING_PONG_RECYCLE_TIME = "ping_pong_recycle_time" +CONF_ROLLING_CODE_ENABLE = "rolling_code_enable" + + +def sensor_validation(cls: MockObjClass): + return cv.maybe_simple_value( + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(cls), + cv.Optional(CONF_BROADCAST_ID): cv.validate_id_name, + } + ), + key=CONF_ID, + ) + + +ENCRYPTION_SCHEMA = { + cv.Optional(CONF_ENCRYPTION): cv.maybe_simple_value( + cv.Schema( + { + cv.Required(CONF_KEY): cv.string, + } + ), + key=CONF_KEY, + ) +} + +PROVIDER_SCHEMA = cv.Schema( + { + cv.Required(CONF_NAME): cv.valid_name, + } +).extend(ENCRYPTION_SCHEMA) + + +def validate_(config): + if CONF_ENCRYPTION in config: + if CONF_SENSORS not in config and CONF_BINARY_SENSORS not in config: + raise cv.Invalid("No sensors or binary sensors to encrypt") + elif config[CONF_ROLLING_CODE_ENABLE]: + raise cv.Invalid("Rolling code requires an encryption key") + if config[CONF_PING_PONG_ENABLE]: + if not any(CONF_ENCRYPTION in p for p in config.get(CONF_PROVIDERS) or ()): + raise cv.Invalid("Ping-pong requires at least one encrypted provider") + return config + + +CONFIG_SCHEMA = cv.All( + cv.polling_component_schema("15s") + .extend( + { + cv.GenerateID(): cv.declare_id(UDPComponent), + cv.Optional(CONF_PORT, default=18511): cv.port, + cv.Optional(CONF_ADDRESSES, default=["255.255.255.255"]): cv.ensure_list( + cv.ipv4 + ), + cv.Optional(CONF_ROLLING_CODE_ENABLE, default=False): cv.boolean, + cv.Optional(CONF_PING_PONG_ENABLE, default=False): cv.boolean, + cv.Optional( + CONF_PING_PONG_RECYCLE_TIME, default="600s" + ): cv.positive_time_period_seconds, + cv.Optional(CONF_SENSORS): cv.ensure_list(sensor_validation(Sensor)), + cv.Optional(CONF_BINARY_SENSORS): cv.ensure_list( + sensor_validation(BinarySensor) + ), + cv.Optional(CONF_PROVIDERS): cv.ensure_list(PROVIDER_SCHEMA), + }, + ) + .extend(ENCRYPTION_SCHEMA), + validate_, +) + +SENSOR_SCHEMA = cv.Schema( + { + cv.Optional(CONF_REMOTE_ID): cv.string_strict, + cv.Required(CONF_PROVIDER): cv.valid_name, + cv.GenerateID(CONF_UDP_ID): cv.use_id(UDPComponent), + } +) + + +def require_internal_with_name(config): + if CONF_NAME in config and CONF_INTERNAL not in config: + raise cv.Invalid("Must provide internal: config when using name:") + return config + + +def hash_encryption_key(config: dict): + return list(hashlib.sha256(config[CONF_KEY].encode()).digest()) + + +async def to_code(config): + cg.add_define("USE_UDP") + cg.add_global(udp_ns.using) + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + cg.add(var.set_port(config[CONF_PORT])) + cg.add(var.set_rolling_code_enable(config[CONF_ROLLING_CODE_ENABLE])) + cg.add(var.set_ping_pong_enable(config[CONF_PING_PONG_ENABLE])) + cg.add( + var.set_ping_pong_recycle_time( + config[CONF_PING_PONG_RECYCLE_TIME].total_seconds + ) + ) + for sens_conf in config.get(CONF_SENSORS, ()): + sens_id = sens_conf[CONF_ID] + sensor = await cg.get_variable(sens_id) + bcst_id = sens_conf.get(CONF_BROADCAST_ID, sens_id.id) + cg.add(var.add_sensor(bcst_id, sensor)) + for sens_conf in config.get(CONF_BINARY_SENSORS, ()): + sens_id = sens_conf[CONF_ID] + sensor = await cg.get_variable(sens_id) + bcst_id = sens_conf.get(CONF_BROADCAST_ID, sens_id.id) + cg.add(var.add_binary_sensor(bcst_id, sensor)) + for address in config[CONF_ADDRESSES]: + cg.add(var.add_address(str(address))) + + if encryption := config.get(CONF_ENCRYPTION): + cg.add(var.set_encryption_key(hash_encryption_key(encryption))) + + for provider in config.get(CONF_PROVIDERS, ()): + name = provider[CONF_NAME] + cg.add(var.add_provider(name)) + if encryption := provider.get(CONF_ENCRYPTION): + cg.add(var.set_provider_encryption(name, hash_encryption_key(encryption))) diff --git a/esphome/components/udp/binary_sensor.py b/esphome/components/udp/binary_sensor.py new file mode 100644 index 0000000000..d90e495527 --- /dev/null +++ b/esphome/components/udp/binary_sensor.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +from esphome.config_validation import All, has_at_least_one_key +from esphome.const import CONF_ID + +from . import ( + CONF_PROVIDER, + CONF_REMOTE_ID, + CONF_UDP_ID, + SENSOR_SCHEMA, + require_internal_with_name, +) + +DEPENDENCIES = ["udp"] + +CONFIG_SCHEMA = All( + binary_sensor.binary_sensor_schema().extend(SENSOR_SCHEMA), + has_at_least_one_key(CONF_ID, CONF_REMOTE_ID), + require_internal_with_name, +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + comp = await cg.get_variable(config[CONF_UDP_ID]) + remote_id = str(config.get(CONF_REMOTE_ID) or config.get(CONF_ID)) + cg.add(comp.add_remote_binary_sensor(config[CONF_PROVIDER], remote_id, var)) diff --git a/esphome/components/udp/sensor.py b/esphome/components/udp/sensor.py new file mode 100644 index 0000000000..860c277c44 --- /dev/null +++ b/esphome/components/udp/sensor.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +from esphome.components.sensor import new_sensor, sensor_schema +from esphome.config_validation import All, has_at_least_one_key +from esphome.const import CONF_ID + +from . import ( + CONF_PROVIDER, + CONF_REMOTE_ID, + CONF_UDP_ID, + SENSOR_SCHEMA, + require_internal_with_name, +) + +DEPENDENCIES = ["udp"] + +CONFIG_SCHEMA = All( + sensor_schema().extend(SENSOR_SCHEMA), + has_at_least_one_key(CONF_ID, CONF_REMOTE_ID), + require_internal_with_name, +) + + +async def to_code(config): + var = await new_sensor(config) + comp = await cg.get_variable(config[CONF_UDP_ID]) + remote_id = str(config.get(CONF_REMOTE_ID) or config.get(CONF_ID)) + cg.add(comp.add_remote_sensor(config[CONF_PROVIDER], remote_id, var)) diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp new file mode 100644 index 0000000000..799ed813d3 --- /dev/null +++ b/esphome/components/udp/udp_component.cpp @@ -0,0 +1,616 @@ +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/components/network/util.h" +#include "udp_component.h" + +namespace esphome { +namespace udp { + +/** + * Structure of a data packet; everything is little-endian + * + * --- In clear text --- + * MAGIC_NUMBER: 16 bits + * host name length: 1 byte + * host name: (length) bytes + * padding: 0 or more null bytes to a 4 byte boundary + * + * --- Encrypted (if key set) ---- + * DATA_KEY: 1 byte: OR ROLLING_CODE_KEY: + * Rolling code (if enabled): 8 bytes + * Ping keys: if any + * repeat: + * PING_KEY: 1 byte + * ping code: 4 bytes + * Sensors: + * repeat: + * SENSOR_KEY: 1 byte + * float value: 4 bytes + * name length: 1 byte + * name + * Binary Sensors: + * repeat: + * BINARY_SENSOR_KEY: 1 byte + * bool value: 1 bytes + * name length: 1 byte + * name + * + * Padded to a 4 byte boundary with nulls + * + * Structure of a ping request packet: + * --- In clear text --- + * MAGIC_PING: 16 bits + * host name length: 1 byte + * host name: (length) bytes + * Ping key (4 bytes) + * + */ +static const char *const TAG = "udp"; + +/** + * XXTEA implementation, using 256 bit key. + */ + +static const uint32_t DELTA = 0x9e3779b9; +#define MX ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((sum ^ y) + (k[(p ^ e) & 7] ^ z))) + +/** + * Encrypt a block of data in-place + */ + +static void xxtea_encrypt(uint32_t *v, size_t n, const uint32_t *k) { + uint32_t z, y, sum, e; + size_t p; + size_t q = 6 + 52 / n; + sum = 0; + z = v[n - 1]; + while (q-- != 0) { + sum += DELTA; + e = (sum >> 2); + for (p = 0; p != n - 1; p++) { + y = v[p + 1]; + z = v[p] += MX; + } + y = v[0]; + z = v[n - 1] += MX; + } +} + +static void xxtea_decrypt(uint32_t *v, size_t n, const uint32_t *k) { + uint32_t z, y, sum, e; + size_t p; + size_t q = 6 + 52 / n; + sum = q * DELTA; + y = v[0]; + while (q-- != 0) { + e = (sum >> 2); + for (p = n - 1; p != 0; p--) { + z = v[p - 1]; + y = v[p] -= MX; + } + z = v[n - 1]; + y = v[0] -= MX; + sum -= DELTA; + } +} + +inline static size_t round4(size_t value) { return (value + 3) & ~3; } + +union FuData { + uint32_t u32; + float f32; +}; + +static const size_t MAX_PACKET_SIZE = 508; +static const uint16_t MAGIC_NUMBER = 0x4553; +static const uint16_t MAGIC_PING = 0x5048; +static const uint32_t PREF_HASH = 0x45535043; +enum DataKey { + ZERO_FILL_KEY, + DATA_KEY, + SENSOR_KEY, + BINARY_SENSOR_KEY, + PING_KEY, + ROLLING_CODE_KEY, +}; + +static const size_t MAX_PING_KEYS = 4; + +static inline void add(std::vector &vec, uint32_t data) { + vec.push_back(data & 0xFF); + vec.push_back((data >> 8) & 0xFF); + vec.push_back((data >> 16) & 0xFF); + vec.push_back((data >> 24) & 0xFF); +} + +static inline uint32_t get_uint32(uint8_t *&buf) { + uint32_t data = *buf++; + data += *buf++ << 8; + data += *buf++ << 16; + data += *buf++ << 24; + return data; +} + +static inline uint16_t get_uint16(uint8_t *&buf) { + uint16_t data = *buf++; + data += *buf++ << 8; + return data; +} + +static inline void add(std::vector &vec, uint8_t data) { vec.push_back(data); } +static inline void add(std::vector &vec, uint16_t data) { + vec.push_back((uint8_t) data); + vec.push_back((uint8_t) (data >> 8)); +} +static inline void add(std::vector &vec, DataKey data) { vec.push_back(data); } +static void add(std::vector &vec, const char *str) { + auto len = strlen(str); + vec.push_back(len); + for (size_t i = 0; i != len; i++) { + vec.push_back(*str++); + } +} + +void UDPComponent::setup() { + this->name_ = App.get_name().c_str(); + if (strlen(this->name_) > 255) { + this->mark_failed(); + this->status_set_error("Device name exceeds 255 chars"); + return; + } + this->resend_ping_key_ = this->ping_pong_enable_; + // restore the upper 32 bits of the rolling code, increment and save. + this->pref_ = global_preferences->make_preference(PREF_HASH, true); + this->pref_.load(&this->rolling_code_[1]); + this->rolling_code_[1]++; + this->pref_.save(&this->rolling_code_[1]); + this->ping_key_ = random_uint32(); + ESP_LOGV(TAG, "Rolling code incremented, upper part now %u", (unsigned) this->rolling_code_[1]); +#ifdef USE_SENSOR + for (auto &sensor : this->sensors_) { + sensor.sensor->add_on_state_callback([this, &sensor](float x) { + this->updated_ = true; + sensor.updated = true; + }); + } +#endif +#ifdef USE_BINARY_SENSOR + for (auto &sensor : this->binary_sensors_) { + sensor.sensor->add_on_state_callback([this, &sensor](bool value) { + this->updated_ = true; + sensor.updated = true; + }); + } +#endif + this->should_send_ = this->ping_pong_enable_; +#ifdef USE_SENSOR + this->should_send_ |= !this->sensors_.empty(); +#endif +#ifdef USE_BINARY_SENSOR + this->should_send_ |= !this->binary_sensors_.empty(); +#endif + this->should_listen_ = !this->providers_.empty() || this->is_encrypted_(); + // initialise the header. This is invariant. + add(this->header_, MAGIC_NUMBER); + add(this->header_, this->name_); + // pad to a multiple of 4 bytes + while (this->header_.size() & 0x3) + this->header_.push_back(0); +#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) + for (const auto &address : this->addresses_) { + struct sockaddr saddr {}; + socket::set_sockaddr(&saddr, sizeof(saddr), address, this->port_); + this->sockaddrs_.push_back(saddr); + } + // set up broadcast socket + if (this->should_send_) { + this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (this->broadcast_socket_ == nullptr) { + this->mark_failed(); + this->status_set_error("Could not create socket"); + return; + } + int enable = 1; + auto err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if (err != 0) { + this->status_set_warning("Socket unable to set reuseaddr"); + // we can still continue + } + err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int)); + if (err != 0) { + this->status_set_warning("Socket unable to set broadcast"); + } + } + // create listening socket if we either want to subscribe to providers, or need to listen + // for ping key broadcasts. + if (this->should_listen_) { + this->listen_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (this->listen_socket_ == nullptr) { + this->mark_failed(); + this->status_set_error("Could not create socket"); + return; + } + auto err = this->listen_socket_->setblocking(false); + if (err < 0) { + ESP_LOGE(TAG, "Unable to set nonblocking: errno %d", errno); + this->mark_failed(); + this->status_set_error("Unable to set nonblocking"); + return; + } + int enable = 1; + err = this->listen_socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + if (err != 0) { + this->status_set_warning("Socket unable to set reuseaddr"); + // we can still continue + } + struct sockaddr_in server {}; + + socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_); + if (sl == 0) { + ESP_LOGE(TAG, "Socket unable to set sockaddr: errno %d", errno); + this->mark_failed(); + this->status_set_error("Unable to set sockaddr"); + return; + } + + err = this->listen_socket_->bind((struct sockaddr *) &server, sizeof(server)); + if (err != 0) { + ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); + this->mark_failed(); + this->status_set_error("Unable to bind socket"); + return; + } + } +#else + // 8266 and RP2040 `Duino + for (const auto &address : this->addresses_) { + auto ipaddr = IPAddress(); + ipaddr.fromString(address.c_str()); + this->ipaddrs_.push_back(ipaddr); + } + if (this->should_listen_) + this->udp_client_.begin(this->port_); +#endif +} + +void UDPComponent::init_data_() { + this->data_.clear(); + if (this->rolling_code_enable_) { + add(this->data_, ROLLING_CODE_KEY); + add(this->data_, this->rolling_code_[0]); + add(this->data_, this->rolling_code_[1]); + this->increment_code_(); + } else { + add(this->data_, DATA_KEY); + } + for (auto pkey : this->ping_keys_) { + add(this->data_, PING_KEY); + add(this->data_, pkey.second); + } +} + +void UDPComponent::flush_() { + if (!network::is_connected() || this->data_.empty()) + return; + uint32_t buffer[MAX_PACKET_SIZE / 4]; + memset(buffer, 0, sizeof buffer); + // len must be a multiple of 4 + auto header_len = round4(this->header_.size()) / 4; + auto len = round4(data_.size()) / 4; + memcpy(buffer, this->header_.data(), this->header_.size()); + memcpy(buffer + header_len, this->data_.data(), this->data_.size()); + if (this->is_encrypted_()) { + xxtea_encrypt(buffer + header_len, len, (uint32_t *) this->encryption_key_.data()); + } + auto total_len = (header_len + len) * 4; + this->send_packet_(buffer, total_len); +} + +void UDPComponent::add_binary_data_(uint8_t key, const char *id, bool data) { + auto len = 1 + 1 + 1 + strlen(id); + if (len + this->header_.size() + this->data_.size() > MAX_PACKET_SIZE) { + this->flush_(); + } + add(this->data_, key); + add(this->data_, (uint8_t) data); + add(this->data_, id); +} +void UDPComponent::add_data_(uint8_t key, const char *id, float data) { + FuData udata{.f32 = data}; + this->add_data_(key, id, udata.u32); +} + +void UDPComponent::add_data_(uint8_t key, const char *id, uint32_t data) { + auto len = 4 + 1 + 1 + strlen(id); + if (len + this->header_.size() + this->data_.size() > MAX_PACKET_SIZE) { + this->flush_(); + } + add(this->data_, key); + add(this->data_, data); + add(this->data_, id); +} +void UDPComponent::send_data_(bool all) { + if (!this->should_send_ || !network::is_connected()) + return; + this->init_data_(); +#ifdef USE_SENSOR + for (auto &sensor : this->sensors_) { + if (all || sensor.updated) { + sensor.updated = false; + this->add_data_(SENSOR_KEY, sensor.id, sensor.sensor->get_state()); + } + } +#endif +#ifdef USE_BINARY_SENSOR + for (auto &sensor : this->binary_sensors_) { + if (all || sensor.updated) { + sensor.updated = false; + this->add_binary_data_(BINARY_SENSOR_KEY, sensor.id, sensor.sensor->state); + } + } +#endif + this->flush_(); + this->updated_ = false; + this->resend_data_ = false; +} + +void UDPComponent::update() { + this->updated_ = true; + this->resend_data_ = this->should_send_; + auto now = millis() / 1000; + if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) { + this->resend_ping_key_ = this->ping_pong_enable_; + this->last_key_time_ = now; + } +} + +void UDPComponent::loop() { + uint8_t buf[MAX_PACKET_SIZE]; + if (this->should_listen_) { + for (;;) { +#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) + auto len = this->listen_socket_->read(buf, sizeof(buf)); +#else + auto len = this->udp_client_.parsePacket(); + if (len > 0) + len = this->udp_client_.read(buf, sizeof(buf)); +#endif + if (len > 0) { + this->process_(buf, len); + continue; + } + break; + } + } + if (this->resend_ping_key_) + this->send_ping_pong_request_(); + if (this->updated_) { + this->send_data_(this->resend_data_); + } +} + +void UDPComponent::add_key_(const char *name, uint32_t key) { + if (!this->is_encrypted_()) + return; + if (this->ping_keys_.count(name) == 0 && this->ping_keys_.size() == MAX_PING_KEYS) { + ESP_LOGW(TAG, "Ping key from %s discarded", name); + return; + } + this->ping_keys_[name] = key; + this->resend_data_ = true; + ESP_LOGV(TAG, "Ping key from %s now %X", name, (unsigned) key); +} + +void UDPComponent::process_ping_request_(const char *name, uint8_t *ptr, size_t len) { + if (len != 4) { + ESP_LOGW(TAG, "Bad ping request"); + return; + } + auto key = get_uint32(ptr); + this->add_key_(name, key); + ESP_LOGV(TAG, "Updated ping key for %s to %08X", name, (unsigned) key); +} + +static bool process_rolling_code(Provider &provider, uint8_t *&buf, const uint8_t *end) { + if (end - buf < 8) + return false; + auto code0 = get_uint32(buf); + auto code1 = get_uint32(buf); + if (code1 < provider.last_code[1] || (code1 == provider.last_code[1] && code0 <= provider.last_code[0])) { + ESP_LOGW(TAG, "Rolling code for %s %08lX:%08lX is old", provider.name, (unsigned long) code1, + (unsigned long) code0); + return false; + } + provider.last_code[0] = code0; + provider.last_code[1] = code1; + return true; +} + +/** + * Process a received packet + */ +void UDPComponent::process_(uint8_t *buf, const size_t len) { + auto ping_key_seen = !this->ping_pong_enable_; + if (len < 8) { + return ESP_LOGV(TAG, "Bad length %zu", len); + } + char namebuf[256]{}; + uint8_t byte; + uint8_t *start_ptr = buf; + const uint8_t *end = buf + len; + FuData rdata{}; + auto magic = get_uint16(buf); + if (magic != MAGIC_NUMBER && magic != MAGIC_PING) + return ESP_LOGV(TAG, "Bad magic %X", magic); + + auto hlen = *buf++; + if (hlen > len - 3) { + return ESP_LOGV(TAG, "Bad hostname length %u > %zu", hlen, len - 3); + } + memcpy(namebuf, buf, hlen); + if (strcmp(this->name_, namebuf) == 0) { + return ESP_LOGV(TAG, "Ignoring our own data"); + } + buf += hlen; + if (magic == MAGIC_PING) + return this->process_ping_request_(namebuf, buf, end - buf); + if (round4(len) != len) { + return ESP_LOGW(TAG, "Bad length %zu", len); + } + hlen = round4(hlen + 3); + buf = start_ptr + hlen; + if (buf == end) { + return ESP_LOGV(TAG, "No data after header"); + } + + if (this->providers_.count(namebuf) == 0) { + return ESP_LOGVV(TAG, "Unknown hostname %s", namebuf); + } + auto &provider = this->providers_[namebuf]; + // if encryption not used with this host, ping check is pointless since it would be easily spoofed. + if (provider.encryption_key.empty()) + ping_key_seen = true; + + ESP_LOGV(TAG, "Found hostname %s", namebuf); +#ifdef USE_SENSOR + auto &sensors = this->remote_sensors_[namebuf]; +#endif +#ifdef USE_BINARY_SENSOR + auto &binary_sensors = this->remote_binary_sensors_[namebuf]; +#endif + + if (!provider.encryption_key.empty()) { + xxtea_decrypt((uint32_t *) buf, (end - buf) / 4, (uint32_t *) provider.encryption_key.data()); + } + byte = *buf++; + if (byte == ROLLING_CODE_KEY) { + if (!process_rolling_code(provider, buf, end)) + return; + } else if (byte != DATA_KEY) { + return ESP_LOGV(TAG, "Expected rolling_key or data_key, got %X", byte); + } + while (buf < end) { + byte = *buf++; + if (byte == ZERO_FILL_KEY) + continue; + if (byte == PING_KEY) { + if (end - buf < 4) { + return ESP_LOGV(TAG, "PING_KEY requires 4 more bytes"); + } + auto key = get_uint32(buf); + if (key == this->ping_key_) { + ping_key_seen = true; + ESP_LOGV(TAG, "Found good ping key %X", (unsigned) key); + } else { + ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key); + } + continue; + } + if (!ping_key_seen) { + ESP_LOGW(TAG, "Ping key not seen"); + this->resend_ping_key_ = true; + break; + } + if (byte == BINARY_SENSOR_KEY) { + if (end - buf < 3) { + return ESP_LOGV(TAG, "Binary sensor key requires at least 3 more bytes"); + } + rdata.u32 = *buf++; + } else if (byte == SENSOR_KEY) { + if (end - buf < 6) { + return ESP_LOGV(TAG, "Sensor key requires at least 6 more bytes"); + } + rdata.u32 = get_uint32(buf); + } else { + return ESP_LOGW(TAG, "Unknown key byte %X", byte); + } + + hlen = *buf++; + if (end - buf < hlen) { + return ESP_LOGV(TAG, "Name length of %u not available", hlen); + } + memset(namebuf, 0, sizeof namebuf); + memcpy(namebuf, buf, hlen); + ESP_LOGV(TAG, "Found sensor key %d, id %s, data %lX", byte, namebuf, (unsigned long) rdata.u32); + buf += hlen; +#ifdef USE_SENSOR + if (byte == SENSOR_KEY && sensors.count(namebuf) != 0) + sensors[namebuf]->publish_state(rdata.f32); +#endif +#ifdef USE_BINARY_SENSOR + if (byte == BINARY_SENSOR_KEY && binary_sensors.count(namebuf) != 0) + binary_sensors[namebuf]->publish_state(rdata.u32 != 0); +#endif + } +} + +void UDPComponent::dump_config() { + ESP_LOGCONFIG(TAG, "UDP:"); + ESP_LOGCONFIG(TAG, " Port: %u", this->port_); + ESP_LOGCONFIG(TAG, " Encrypted: %s", YESNO(this->is_encrypted_())); + ESP_LOGCONFIG(TAG, " Ping-pong: %s", YESNO(this->ping_pong_enable_)); + for (const auto &address : this->addresses_) + ESP_LOGCONFIG(TAG, " Address: %s", address.c_str()); +#ifdef USE_SENSOR + for (auto sensor : this->sensors_) + ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.id); +#endif +#ifdef USE_BINARY_SENSOR + for (auto sensor : this->binary_sensors_) + ESP_LOGCONFIG(TAG, " Binary Sensor: %s", sensor.id); +#endif + for (const auto &host : this->providers_) { + ESP_LOGCONFIG(TAG, " Remote host: %s", host.first.c_str()); + ESP_LOGCONFIG(TAG, " Encrypted: %s", YESNO(!host.second.encryption_key.empty())); +#ifdef USE_SENSOR + for (const auto &sensor : this->remote_sensors_[host.first.c_str()]) + ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.first.c_str()); +#endif +#ifdef USE_BINARY_SENSOR + for (const auto &sensor : this->remote_binary_sensors_[host.first.c_str()]) + ESP_LOGCONFIG(TAG, " Binary Sensor: %s", sensor.first.c_str()); +#endif + } +} +void UDPComponent::increment_code_() { + if (this->rolling_code_enable_) { + if (++this->rolling_code_[0] == 0) { + this->rolling_code_[1]++; + this->pref_.save(&this->rolling_code_[1]); + } + } +} +void UDPComponent::send_packet_(void *data, size_t len) { +#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) + for (const auto &saddr : this->sockaddrs_) { + auto result = this->broadcast_socket_->sendto(data, len, 0, &saddr, sizeof(saddr)); + if (result < 0) + ESP_LOGW(TAG, "sendto() error %d", errno); + } +#else + auto iface = IPAddress(0, 0, 0, 0); + for (const auto &saddr : this->ipaddrs_) { + if (this->udp_client_.beginPacketMulticast(saddr, this->port_, iface, 128) != 0) { + this->udp_client_.write((const uint8_t *) data, len); + auto result = this->udp_client_.endPacket(); + if (result == 0) + ESP_LOGW(TAG, "udp.write() error"); + } + } +#endif +} + +void UDPComponent::send_ping_pong_request_() { + if (!this->ping_pong_enable_ || !network::is_connected()) + return; + this->ping_key_ = random_uint32(); + this->ping_header_.clear(); + add(this->ping_header_, MAGIC_PING); + add(this->ping_header_, this->name_); + add(this->ping_header_, this->ping_key_); + this->send_packet_(this->ping_header_.data(), this->ping_header_.size()); + this->resend_ping_key_ = false; + ESP_LOGV(TAG, "Sent new ping request %08X", (unsigned) this->ping_key_); +} +} // namespace udp +} // namespace esphome diff --git a/esphome/components/udp/udp_component.h b/esphome/components/udp/udp_component.h new file mode 100644 index 0000000000..69bf335a90 --- /dev/null +++ b/esphome/components/udp/udp_component.h @@ -0,0 +1,158 @@ +#pragma once + +#include "esphome/core/component.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) +#include "esphome/components/socket/socket.h" +#else +#include +#endif +#include +#include + +namespace esphome { +namespace udp { + +struct Provider { + std::vector encryption_key; + const char *name; + uint32_t last_code[2]; +}; + +#ifdef USE_SENSOR +struct Sensor { + sensor::Sensor *sensor; + const char *id; + bool updated; +}; +#endif +#ifdef USE_BINARY_SENSOR +struct BinarySensor { + binary_sensor::BinarySensor *sensor; + const char *id; + bool updated; +}; +#endif + +class UDPComponent : public PollingComponent { + public: + void setup() override; + void loop() override; + void update() override; + void dump_config() override; + +#ifdef USE_SENSOR + void add_sensor(const char *id, sensor::Sensor *sensor) { + Sensor st{sensor, id, true}; + this->sensors_.push_back(st); + } + void add_remote_sensor(const char *hostname, const char *remote_id, sensor::Sensor *sensor) { + this->add_provider(hostname); + this->remote_sensors_[hostname][remote_id] = sensor; + } +#endif +#ifdef USE_BINARY_SENSOR + void add_binary_sensor(const char *id, binary_sensor::BinarySensor *sensor) { + BinarySensor st{sensor, id, true}; + this->binary_sensors_.push_back(st); + } + + void add_remote_binary_sensor(const char *hostname, const char *remote_id, binary_sensor::BinarySensor *sensor) { + this->add_provider(hostname); + this->remote_binary_sensors_[hostname][remote_id] = sensor; + } +#endif + void add_address(const char *addr) { this->addresses_.emplace_back(addr); } + void set_port(uint16_t port) { this->port_ = port; } + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + + void add_provider(const char *hostname) { + if (this->providers_.count(hostname) == 0) { + Provider provider; + provider.encryption_key = std::vector{}; + provider.last_code[0] = 0; + provider.last_code[1] = 0; + provider.name = hostname; + this->providers_[hostname] = provider; +#ifdef USE_SENSOR + this->remote_sensors_[hostname] = std::map(); +#endif +#ifdef USE_BINARY_SENSOR + this->remote_binary_sensors_[hostname] = std::map(); +#endif + } + } + + void set_encryption_key(std::vector key) { this->encryption_key_ = std::move(key); } + void set_rolling_code_enable(bool enable) { this->rolling_code_enable_ = enable; } + void set_ping_pong_enable(bool enable) { this->ping_pong_enable_ = enable; } + void set_ping_pong_recycle_time(uint32_t recycle_time) { this->ping_pong_recyle_time_ = recycle_time; } + void set_provider_encryption(const char *name, std::vector key) { + this->providers_[name].encryption_key = std::move(key); + } + + protected: + void send_data_(bool all); + void process_(uint8_t *buf, size_t len); + void flush_(); + void add_data_(uint8_t key, const char *id, float data); + void add_data_(uint8_t key, const char *id, uint32_t data); + void increment_code_(); + void add_binary_data_(uint8_t key, const char *id, bool data); + void init_data_(); + + bool updated_{}; + uint16_t port_{18511}; + uint32_t ping_key_{}; + uint32_t rolling_code_[2]{}; + bool rolling_code_enable_{}; + bool ping_pong_enable_{}; + uint32_t ping_pong_recyle_time_{}; + uint32_t last_key_time_{}; + bool resend_ping_key_{}; + bool resend_data_{}; + bool should_send_{}; + const char *name_{}; + bool should_listen_{}; + ESPPreferenceObject pref_; + +#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) + std::unique_ptr broadcast_socket_ = nullptr; + std::unique_ptr listen_socket_ = nullptr; + std::vector sockaddrs_{}; +#else + std::vector ipaddrs_{}; + WiFiUDP udp_client_{}; +#endif + std::vector encryption_key_{}; + std::vector addresses_{}; + +#ifdef USE_SENSOR + std::vector sensors_{}; + std::map> remote_sensors_{}; +#endif +#ifdef USE_BINARY_SENSOR + std::vector binary_sensors_{}; + std::map> remote_binary_sensors_{}; +#endif + + std::map providers_{}; + std::vector ping_header_{}; + std::vector header_{}; + std::vector data_{}; + std::map ping_keys_{}; + void add_key_(const char *name, uint32_t key); + void send_ping_pong_request_(); + void send_packet_(void *data, size_t len); + void process_ping_request_(const char *name, uint8_t *ptr, size_t len); + + inline bool is_encrypted_() { return !this->encryption_key_.empty(); } +}; + +} // namespace udp +} // namespace esphome diff --git a/tests/components/udp/common.yaml b/tests/components/udp/common.yaml new file mode 100644 index 0000000000..3bdc19ece5 --- /dev/null +++ b/tests/components/udp/common.yaml @@ -0,0 +1,35 @@ +wifi: + ssid: MySSID + password: password1 + +udp: + update_interval: 5s + encryption: "our key goes here" + rolling_code_enable: true + ping_pong_enable: true + binary_sensors: + - binary_sensor_id1 + - id: binary_sensor_id1 + broadcast_id: other_id + sensors: + - sensor_id1 + - id: sensor_id1 + broadcast_id: other_id + providers: + - name: some-device-name + encryption: "their key goes here" + +sensor: + - platform: template + id: sensor_id1 + - platform: udp + provider: some-device-name + id: our_id + remote_id: some_sensor_id + +binary_sensor: + - platform: udp + provider: unencrypted-device + id: other_binary_sensor_id + - platform: template + id: binary_sensor_id1 diff --git a/tests/components/udp/test.bk72xx-ard.yaml b/tests/components/udp/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/udp/test.bk72xx-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/udp/test.esp32-ard.yaml b/tests/components/udp/test.esp32-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/udp/test.esp32-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/udp/test.esp32-c3-ard.yaml b/tests/components/udp/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/udp/test.esp32-c3-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/udp/test.esp32-c3-idf.yaml b/tests/components/udp/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/udp/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/udp/test.esp32-idf.yaml b/tests/components/udp/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/udp/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/udp/test.esp8266-ard.yaml b/tests/components/udp/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/udp/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/udp/test.host.yaml b/tests/components/udp/test.host.yaml new file mode 100644 index 0000000000..e735c37e4d --- /dev/null +++ b/tests/components/udp/test.host.yaml @@ -0,0 +1,4 @@ +packages: + common: !include common.yaml + +wifi: !remove diff --git a/tests/components/udp/test.rp2040-ard.yaml b/tests/components/udp/test.rp2040-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/udp/test.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml From ca2f25e73bc04858d488540373475e600542d415 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Sun, 1 Sep 2024 13:20:31 +0200 Subject: [PATCH 24/55] update logs for bluetooth proxy (#7382) --- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index f188439d0e..bd1c8b7ea4 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -54,6 +54,9 @@ bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_p } resp.advertisements.push_back(std::move(adv)); + + ESP_LOGV(TAG, "Proxying raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0], + result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi); } ESP_LOGV(TAG, "Proxying %d packets", count); this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp); @@ -87,6 +90,8 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_)); + ESP_LOGCONFIG(TAG, " Connections: %d", this->connections_.size()); + ESP_LOGCONFIG(TAG, " Raw advertisements: %s", YESNO(this->raw_advertisements_)); } int BluetoothProxy::get_bluetooth_connections_free() { From 61223a3cc9df12d07e9c8e288a691ef5cc1b30fd Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 2 Sep 2024 06:45:40 +1000 Subject: [PATCH 25/55] [font] Make display an auto-load, not a dependency (#7366) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/display/__init__.py | 9 +++---- esphome/components/font/__init__.py | 33 +++++++++----------------- esphome/components/font/font.cpp | 5 ++-- esphome/components/font/font.h | 15 +++++++++--- esphome/core/defines.h | 1 + tests/components/font/test.host.yaml | 23 ++++++++++++++++++ 6 files changed, 55 insertions(+), 31 deletions(-) create mode 100644 tests/components/font/test.host.yaml diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index c4bb12b75d..32a8b3b090 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -1,15 +1,15 @@ +from esphome import automation, core +from esphome.automation import maybe_simple_id import esphome.codegen as cg import esphome.config_validation as cv -from esphome import core, automation -from esphome.automation import maybe_simple_id from esphome.const import ( CONF_AUTO_CLEAR_ENABLED, + CONF_FROM, CONF_ID, CONF_LAMBDA, - CONF_PAGES, CONF_PAGE_ID, + CONF_PAGES, CONF_ROTATION, - CONF_FROM, CONF_TO, CONF_TRIGGER_ID, ) @@ -195,3 +195,4 @@ async def display_is_displaying_page_to_code(config, condition_id, template_arg, @coroutine_with_priority(100.0) async def to_code(config): cg.add_global(display_ns.using) + cg.add_define("USE_DISPLAY") diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 7e4674ffda..b5ed02e89a 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -1,43 +1,35 @@ +import functools import hashlib import logging - -import functools -from pathlib import Path import os +from pathlib import Path import re + from packaging import version import requests -from esphome import core -from esphome import external_files -import esphome.config_validation as cv +from esphome import core, external_files import esphome.codegen as cg -from esphome.helpers import ( - copy_file_if_changed, - cpp_string_escape, -) +import esphome.config_validation as cv from esphome.const import ( CONF_FAMILY, CONF_FILE, CONF_GLYPHS, CONF_ID, + CONF_PATH, CONF_RAW_DATA_ID, - CONF_TYPE, CONF_REFRESH, CONF_SIZE, - CONF_PATH, - CONF_WEIGHT, + CONF_TYPE, CONF_URL, + CONF_WEIGHT, ) -from esphome.core import ( - CORE, - HexInt, -) +from esphome.core import CORE, HexInt +from esphome.helpers import copy_file_if_changed, cpp_string_escape _LOGGER = logging.getLogger(__name__) DOMAIN = "font" -DEPENDENCIES = ["display"] MULTI_CONF = True CODEOWNERS = ["@esphome/core", "@clydebarrow"] @@ -400,10 +392,7 @@ class EFont: def convert_bitmap_to_pillow_font(filepath): - from PIL import ( - PcfFontFile, - BdfFontFile, - ) + from PIL import BdfFontFile, PcfFontFile local_bitmap_font_file = external_files.compute_local_file_dir( DOMAIN, diff --git a/esphome/components/font/font.cpp b/esphome/components/font/font.cpp index 3b62b8ca66..aeca0f5cc0 100644 --- a/esphome/components/font/font.cpp +++ b/esphome/components/font/font.cpp @@ -1,9 +1,8 @@ #include "font.h" +#include "esphome/core/color.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" -#include "esphome/core/color.h" -#include "esphome/components/display/display_buffer.h" namespace esphome { namespace font { @@ -68,6 +67,7 @@ int Font::match_next_glyph(const uint8_t *str, int *match_length) { return -1; return lo; } +#ifdef USE_DISPLAY void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) { *baseline = this->baseline_; *height = this->height_; @@ -164,6 +164,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo i += match_length; } } +#endif } // namespace font } // namespace esphome diff --git a/esphome/components/font/font.h b/esphome/components/font/font.h index 57002cf510..5cde694d91 100644 --- a/esphome/components/font/font.h +++ b/esphome/components/font/font.h @@ -1,8 +1,11 @@ #pragma once -#include "esphome/core/datatypes.h" #include "esphome/core/color.h" -#include "esphome/components/display/display_buffer.h" +#include "esphome/core/datatypes.h" +#include "esphome/core/defines.h" +#ifdef USE_DISPLAY +#include "esphome/components/display/display.h" +#endif namespace esphome { namespace font { @@ -38,7 +41,11 @@ class Glyph { const GlyphData *glyph_data_; }; -class Font : public display::BaseFont { +class Font +#ifdef USE_DISPLAY + : public display::BaseFont +#endif +{ public: /** Construct the font with the given glyphs. * @@ -50,9 +57,11 @@ class Font : public display::BaseFont { int match_next_glyph(const uint8_t *str, int *match_length); +#ifdef USE_DISPLAY void print(int x_start, int y_start, display::Display *display, Color color, const char *text, Color background) override; void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override; +#endif inline int get_baseline() { return this->baseline_; } inline int get_height() { return this->height_; } inline int get_bpp() { return this->bpp_; } diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 52cf7d4dd0..ffd5cc6f1b 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -28,6 +28,7 @@ #define USE_DATETIME_DATETIME #define USE_DATETIME_TIME #define USE_DEEP_SLEEP +#define USE_DISPLAY #define USE_EVENT #define USE_FAN #define USE_GRAPH diff --git a/tests/components/font/test.host.yaml b/tests/components/font/test.host.yaml new file mode 100644 index 0000000000..017328ec83 --- /dev/null +++ b/tests/components/font/test.host.yaml @@ -0,0 +1,23 @@ +font: + - file: "gfonts://Roboto" + id: roboto + size: 20 + glyphs: "0123456789." + extras: + - file: "gfonts://Roboto" + glyphs: ["\u00C4", "\u00C5", "\U000000C7"] + - file: "gfonts://Roboto" + id: roboto_web + size: 20 + - file: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" + id: monocraft + size: 20 + - file: + type: web + url: "https://github.com/IdreesInc/Monocraft/releases/download/v3.0/Monocraft.ttf" + id: monocraft2 + size: 24 + - file: $component_dir/Monocraft.ttf + id: monocraft3 + size: 28 + From 3a7aabb2eb3fe58c2925cd3f5e1f605e15797b1a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 2 Sep 2024 10:36:18 +1200 Subject: [PATCH 26/55] Bump Dockerfile dependencies (#7386) --- docker/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 16f37274c6..4393d5a447 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -34,8 +34,8 @@ RUN \ python3-wheel=0.38.4-2 \ iputils-ping=3:20221126-1 \ git=1:2.39.2-1.1 \ - curl=7.88.1-10+deb12u6 \ - openssh-client=1:9.2p1-2+deb12u2 \ + curl=7.88.1-10+deb12u7 \ + openssh-client=1:9.2p1-2+deb12u3 \ python3-cffi=1.15.1-5 \ libcairo2=1.16.0-7 \ libmagic1=1:5.44-3 \ @@ -49,7 +49,7 @@ RUN \ zlib1g-dev=1:1.2.13.dfsg-1 \ libjpeg-dev=1:2.1.5-2 \ libfreetype-dev=2.12.1+dfsg-5+deb12u3 \ - libssl-dev=3.0.13-1~deb12u1 \ + libssl-dev=3.0.14-1~deb12u1 \ libffi-dev=3.4.4-1 \ libopenjp2-7=2.5.0-2 \ libtiff6=4.5.0-6+deb12u1 \ From 6490fc9c620d602fae2957f31e2756be886354ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Skowro=C5=84ski?= Date: Mon, 2 Sep 2024 03:56:35 +0200 Subject: [PATCH 27/55] CH422G support (#7356) --- CODEOWNERS | 1 + esphome/components/ch422g/__init__.py | 67 ++++++++++ esphome/components/ch422g/ch422g.cpp | 122 ++++++++++++++++++ esphome/components/ch422g/ch422g.h | 70 ++++++++++ tests/components/ch422g/common.yaml | 20 +++ tests/components/ch422g/test.esp32-ard.yaml | 6 + .../components/ch422g/test.esp32-c3-ard.yaml | 6 + .../components/ch422g/test.esp32-c3-idf.yaml | 6 + tests/components/ch422g/test.esp32-idf.yaml | 6 + tests/components/ch422g/test.esp8266-ard.yaml | 6 + tests/components/ch422g/test.rp2040-ard.yaml | 6 + 11 files changed, 316 insertions(+) create mode 100644 esphome/components/ch422g/__init__.py create mode 100644 esphome/components/ch422g/ch422g.cpp create mode 100644 esphome/components/ch422g/ch422g.h create mode 100644 tests/components/ch422g/common.yaml create mode 100644 tests/components/ch422g/test.esp32-ard.yaml create mode 100644 tests/components/ch422g/test.esp32-c3-ard.yaml create mode 100644 tests/components/ch422g/test.esp32-c3-idf.yaml create mode 100644 tests/components/ch422g/test.esp32-idf.yaml create mode 100644 tests/components/ch422g/test.esp8266-ard.yaml create mode 100644 tests/components/ch422g/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 807829eafd..ab11086980 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -83,6 +83,7 @@ esphome/components/cap1188/* @mreditor97 esphome/components/captive_portal/* @OttoWinter esphome/components/ccs811/* @habbie esphome/components/cd74hc4067/* @asoehlke +esphome/components/ch422g/* @jesterret esphome/components/climate/* @esphome/core esphome/components/climate_ir/* @glmnet esphome/components/color_temperature/* @jesserockz diff --git a/esphome/components/ch422g/__init__.py b/esphome/components/ch422g/__init__.py new file mode 100644 index 0000000000..cf8b5f65d3 --- /dev/null +++ b/esphome/components/ch422g/__init__.py @@ -0,0 +1,67 @@ +from esphome import pins +import esphome.codegen as cg +from esphome.components import i2c +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, + CONF_RESTORE_VALUE, +) + +CODEOWNERS = ["@jesterret"] +DEPENDENCIES = ["i2c"] +MULTI_CONF = True +ch422g_ns = cg.esphome_ns.namespace("ch422g") + +CH422GComponent = ch422g_ns.class_("CH422GComponent", cg.Component, i2c.I2CDevice) +CH422GGPIOPin = ch422g_ns.class_( + "CH422GGPIOPin", cg.GPIOPin, cg.Parented.template(CH422GComponent) +) + +CONF_CH422G = "ch422g" +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(CH422GComponent), + cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x24)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + +CH422G_PIN_SCHEMA = pins.gpio_base_schema( + CH422GGPIOPin, + cv.int_range(min=0, max=7), + modes=[CONF_INPUT, CONF_OUTPUT], +).extend( + { + cv.Required(CONF_CH422G): cv.use_id(CH422GComponent), + } +) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA) +async def ch422g_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + parent = await cg.get_variable(config[CONF_CH422G]) + + cg.add(var.set_parent(parent)) + + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/ch422g/ch422g.cpp b/esphome/components/ch422g/ch422g.cpp new file mode 100644 index 0000000000..25038991ed --- /dev/null +++ b/esphome/components/ch422g/ch422g.cpp @@ -0,0 +1,122 @@ +#include "ch422g.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ch422g { + +const uint8_t CH422G_REG_IN = 0x26; +const uint8_t CH422G_REG_OUT = 0x38; +const uint8_t OUT_REG_DEFAULT_VAL = 0xdf; + +static const char *const TAG = "ch422g"; + +void CH422GComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up CH422G..."); + // Test to see if device exists + if (!this->read_inputs_()) { + ESP_LOGE(TAG, "CH422G not detected at 0x%02X", this->address_); + this->mark_failed(); + return; + } + + // restore defaults over whatever got saved on last boot + if (!this->restore_value_) { + this->write_output_(OUT_REG_DEFAULT_VAL); + } + + ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(), + this->status_has_error()); +} + +void CH422GComponent::loop() { + // Clear all the previously read flags. + this->pin_read_cache_ = 0x00; +} + +void CH422GComponent::dump_config() { + ESP_LOGCONFIG(TAG, "CH422G:"); + LOG_I2C_DEVICE(this) + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with CH422G failed!"); + } +} + +// ch422g doesn't have any flag support (needs docs?) +void CH422GComponent::pin_mode(uint8_t pin, gpio::Flags flags) {} + +bool CH422GComponent::digital_read(uint8_t pin) { + if (this->pin_read_cache_ == 0 || this->pin_read_cache_ & (1 << pin)) { + // Read values on first access or in case it's being read again in the same loop + this->read_inputs_(); + } + + this->pin_read_cache_ |= (1 << pin); + return this->state_mask_ & (1 << pin); +} + +void CH422GComponent::digital_write(uint8_t pin, bool value) { + if (value) { + this->write_output_(this->state_mask_ | (1 << pin)); + } else { + this->write_output_(this->state_mask_ & ~(1 << pin)); + } +} + +bool CH422GComponent::read_inputs_() { + if (this->is_failed()) { + return false; + } + + uint8_t temp = 0; + if ((this->last_error_ = this->read(&temp, 1)) != esphome::i2c::ERROR_OK) { + this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str()); + return false; + } + + uint8_t output = 0; + if ((this->last_error_ = this->bus_->read(CH422G_REG_IN, &output, 1)) != esphome::i2c::ERROR_OK) { + this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str()); + return false; + } + + this->state_mask_ = output; + this->status_clear_warning(); + + return true; +} + +bool CH422GComponent::write_output_(uint8_t value) { + const uint8_t temp = 1; + if ((this->last_error_ = this->write(&temp, 1, false)) != esphome::i2c::ERROR_OK) { + this->status_set_warning(str_sprintf("write_output_(): I2C I/O error: %d", (int) this->last_error_).c_str()); + return false; + } + + uint8_t write_mask = value; + if ((this->last_error_ = this->bus_->write(CH422G_REG_OUT, &write_mask, 1)) != esphome::i2c::ERROR_OK) { + this->status_set_warning( + str_sprintf("write_output_(): I2C I/O error: %d for write_mask: %d", (int) this->last_error_, (int) write_mask) + .c_str()); + return false; + } + + this->state_mask_ = value; + this->status_clear_warning(); + return true; +} + +float CH422GComponent::get_setup_priority() const { return setup_priority::IO; } + +// Run our loop() method very early in the loop, so that we cache read values +// before other components call our digital_read() method. +float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI + +void CH422GGPIOPin::setup() { pin_mode(flags_); } +void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } +bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } + +void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } +std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); } + +} // namespace ch422g +} // namespace esphome diff --git a/esphome/components/ch422g/ch422g.h b/esphome/components/ch422g/ch422g.h new file mode 100644 index 0000000000..781df65437 --- /dev/null +++ b/esphome/components/ch422g/ch422g.h @@ -0,0 +1,70 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace ch422g { + +class CH422GComponent : public Component, public i2c::I2CDevice { + public: + CH422GComponent() = default; + + /// Check i2c availability and setup masks + void setup() override; + /// Poll for input changes periodically + void loop() override; + /// Helper function to read the value of a pin. + bool digital_read(uint8_t pin); + /// Helper function to write the value of a pin. + void digital_write(uint8_t pin, bool value); + /// Helper function to set the pin mode of a pin. + void pin_mode(uint8_t pin, gpio::Flags flags); + + float get_setup_priority() const override; + + float get_loop_priority() const override; + + void dump_config() override; + + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } + + protected: + bool read_inputs_(); + + bool write_output_(uint8_t value); + + /// The mask to write as output state - 1 means HIGH, 0 means LOW + uint8_t state_mask_{0x00}; + /// Flags to check if read previously during this loop + uint8_t pin_read_cache_ = {0x00}; + /// Storage for last I2C error seen + esphome::i2c::ErrorCode last_error_; + /// Whether we want to override stored values on expander + bool restore_value_{false}; +}; + +/// Helper class to expose a CH422G pin as an internal input GPIO pin. +class CH422GGPIOPin : public GPIOPin { + public: + void setup() override; + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(CH422GComponent *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + protected: + CH422GComponent *parent_; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +} // namespace ch422g +} // namespace esphome diff --git a/tests/components/ch422g/common.yaml b/tests/components/ch422g/common.yaml new file mode 100644 index 0000000000..02061bda59 --- /dev/null +++ b/tests/components/ch422g/common.yaml @@ -0,0 +1,20 @@ +ch422g: + - id: ch422g_hub + address: 0x24 + +binary_sensor: + - platform: gpio + id: ch422g_input + name: CH422G Binary Sensor + pin: + ch422g: ch422g_hub + number: 1 + mode: INPUT + inverted: true + - platform: gpio + id: ch422g_output + pin: + ch422g: ch422g_hub + number: 0 + mode: OUTPUT + inverted: false diff --git a/tests/components/ch422g/test.esp32-ard.yaml b/tests/components/ch422g/test.esp32-ard.yaml new file mode 100644 index 0000000000..cd3f1bbeef --- /dev/null +++ b/tests/components/ch422g/test.esp32-ard.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_ch422g + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/ch422g/test.esp32-c3-ard.yaml b/tests/components/ch422g/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..cd822cb308 --- /dev/null +++ b/tests/components/ch422g/test.esp32-c3-ard.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_ch422g + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/ch422g/test.esp32-c3-idf.yaml b/tests/components/ch422g/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..cd822cb308 --- /dev/null +++ b/tests/components/ch422g/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_ch422g + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/ch422g/test.esp32-idf.yaml b/tests/components/ch422g/test.esp32-idf.yaml new file mode 100644 index 0000000000..cd3f1bbeef --- /dev/null +++ b/tests/components/ch422g/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_ch422g + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/ch422g/test.esp8266-ard.yaml b/tests/components/ch422g/test.esp8266-ard.yaml new file mode 100644 index 0000000000..cd822cb308 --- /dev/null +++ b/tests/components/ch422g/test.esp8266-ard.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_ch422g + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/ch422g/test.rp2040-ard.yaml b/tests/components/ch422g/test.rp2040-ard.yaml new file mode 100644 index 0000000000..cd822cb308 --- /dev/null +++ b/tests/components/ch422g/test.rp2040-ard.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_ch422g + scl: 5 + sda: 4 + +<<: !include common.yaml From fc930327b48989eb31e1cb38f45b5aeafb09c042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Mon, 2 Sep 2024 04:30:13 +0200 Subject: [PATCH 28/55] [rpi_dpi_rgb] Add enable_pin and reset_display method to driver (#7383) Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/rpi_dpi_rgb/display.py | 6 ++++++ esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp | 18 ++++++++++++++++++ esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h | 3 +++ 3 files changed, 27 insertions(+) diff --git a/esphome/components/rpi_dpi_rgb/display.py b/esphome/components/rpi_dpi_rgb/display.py index 969b9db78e..6cc8d2c27b 100644 --- a/esphome/components/rpi_dpi_rgb/display.py +++ b/esphome/components/rpi_dpi_rgb/display.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import display from esphome.const import ( + CONF_ENABLE_PIN, CONF_HSYNC_PIN, CONF_RESET_PIN, CONF_DATA_PINS, @@ -112,6 +113,7 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_PCLK_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_HSYNC_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_HSYNC_PULSE_WIDTH, default=10): cv.int_, cv.Optional(CONF_HSYNC_BACK_PORCH, default=10): cv.int_, @@ -164,6 +166,10 @@ async def to_code(config): cg.add(var.add_data_pin(data_pin, index)) index += 1 + if enable_pin := config.get(CONF_ENABLE_PIN): + enable = await cg.gpio_pin_expression(enable_pin) + cg.add(var.set_enable_pin(enable)) + if reset_pin := config.get(CONF_RESET_PIN): reset = await cg.gpio_pin_expression(reset_pin) cg.add(var.set_reset_pin(reset)) diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp index 2ffdb3272a..f173a2ec44 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp @@ -104,12 +104,30 @@ void RpiDpiRgb::dump_config() { ESP_LOGCONFIG(TAG, " Height: %u", this->height_); ESP_LOGCONFIG(TAG, " Width: %u", this->width_); LOG_PIN(" DE Pin: ", this->de_pin_); + LOG_PIN(" Enable Pin: ", this->enable_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]); for (size_t i = 0; i != data_pin_count; i++) ESP_LOGCONFIG(TAG, " Data pin %d: %s", i, (this->data_pins_[i])->dump_summary().c_str()); } +void RpiDpiRgb::reset_display_() const { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(false); + if (this->enable_pin_ != nullptr) { + this->enable_pin_->setup(); + this->enable_pin_->digital_write(false); + } + delay(1); + this->reset_pin_->digital_write(true); + if (this->enable_pin_ != nullptr) { + delay(11); + this->enable_pin_->digital_write(true); + } + } +} + } // namespace rpi_dpi_rgb } // namespace esphome diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h index 0319b46391..6d9d6d4ae9 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h @@ -36,6 +36,7 @@ class RpiDpiRgb : public display::Display { void set_pclk_pin(InternalGPIOPin *pclk_pin) { this->pclk_pin_ = pclk_pin; } void set_vsync_pin(InternalGPIOPin *vsync_pin) { this->vsync_pin_ = vsync_pin; } void set_hsync_pin(InternalGPIOPin *hsync_pin) { this->hsync_pin_ = hsync_pin; } + void set_enable_pin(GPIOPin *enable_pin) { this->enable_pin_ = enable_pin; } void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } void set_width(uint16_t width) { this->width_ = width; } void set_dimensions(uint16_t width, uint16_t height) { @@ -62,10 +63,12 @@ class RpiDpiRgb : public display::Display { protected: int get_width_internal() override { return this->width_; } int get_height_internal() override { return this->height_; } + void reset_display_() const; InternalGPIOPin *de_pin_{nullptr}; InternalGPIOPin *pclk_pin_{nullptr}; InternalGPIOPin *hsync_pin_{nullptr}; InternalGPIOPin *vsync_pin_{nullptr}; + GPIOPin *enable_pin_{nullptr}; GPIOPin *reset_pin_{nullptr}; InternalGPIOPin *data_pins_[16] = {}; uint16_t hsync_front_porch_ = 8; From 094c867fba5e329af1f6795318dee1936ccf5b35 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Mon, 2 Sep 2024 04:32:34 +0200 Subject: [PATCH 29/55] Enable IPv6 when manual IPv4 is enabled (#7381) --- esphome/components/ethernet/ethernet_component.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 962a864a29..fdb6eb2da0 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -472,13 +472,13 @@ void EthernetComponent::start_connect_() { if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { ESPHL_ERROR_CHECK(err, "DHCPC start error"); } -#if USE_NETWORK_IPV6 - err = esp_netif_create_ip6_linklocal(this->eth_netif_); - if (err != ESP_OK) { - ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed"); - } -#endif /* USE_NETWORK_IPV6 */ } +#if USE_NETWORK_IPV6 + err = esp_netif_create_ip6_linklocal(this->eth_netif_); + if (err != ESP_OK) { + ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed"); + } +#endif /* USE_NETWORK_IPV6 */ this->connect_begin_ = millis(); this->status_set_warning(); From 854bafbd4a86316168717af4c1ebc72b55da48a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:33:58 +1200 Subject: [PATCH 30/55] Bump actions/upload-artifact from 4.3.4 to 4.4.0 (#7379) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 937c7aac90..7895e7624a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -141,7 +141,7 @@ jobs: echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT - name: Upload digests - uses: actions/upload-artifact@v4.3.4 + uses: actions/upload-artifact@v4.4.0 with: name: digests-${{ steps.sanitize.outputs.name }} path: /tmp/digests From ca8e45cf4c35785ac4417a3349825d26e21ad305 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 2 Sep 2024 19:11:21 +1200 Subject: [PATCH 31/55] [core] Only clean build files with esp-idf (#7388) --- esphome/storage_json.py | 16 ++++++++++++++++ esphome/writer.py | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/esphome/storage_json.py b/esphome/storage_json.py index e2e7514904..2d12ee01a0 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -48,6 +48,8 @@ class StorageJSON: firmware_bin_path: str, loaded_integrations: set[str], no_mdns: bool, + framework: str | None = None, + core_platform: str | None = None, ) -> None: # Version of the storage JSON schema assert storage_version is None or isinstance(storage_version, int) @@ -78,6 +80,10 @@ class StorageJSON: self.loaded_integrations = loaded_integrations # Is mDNS disabled self.no_mdns = no_mdns + # The framework used to compile the firmware + self.framework = framework + # The core platform of this firmware. Like "esp32", "rp2040", "host" etc. + self.core_platform = core_platform def as_dict(self): return { @@ -94,6 +100,8 @@ class StorageJSON: "firmware_bin_path": self.firmware_bin_path, "loaded_integrations": sorted(self.loaded_integrations), "no_mdns": self.no_mdns, + "framework": self.framework, + "core_platform": self.core_platform, } def to_json(self): @@ -127,6 +135,8 @@ class StorageJSON: and CONF_DISABLED in esph.config[CONF_MDNS] and esph.config[CONF_MDNS][CONF_DISABLED] is True ), + framework=esph.target_framework, + core_platform=esph.target_platform, ) @staticmethod @@ -147,6 +157,8 @@ class StorageJSON: firmware_bin_path=None, loaded_integrations=set(), no_mdns=False, + framework=None, + core_platform=platform.lower(), ) @staticmethod @@ -168,6 +180,8 @@ class StorageJSON: firmware_bin_path = storage.get("firmware_bin_path") loaded_integrations = set(storage.get("loaded_integrations", [])) no_mdns = storage.get("no_mdns", False) + framework = storage.get("framework") + core_platform = storage.get("core_platform") return StorageJSON( storage_version, name, @@ -182,6 +196,8 @@ class StorageJSON: firmware_bin_path, loaded_integrations, no_mdns, + framework, + core_platform, ) @staticmethod diff --git a/esphome/writer.py b/esphome/writer.py index 57435d3463..79ee72996c 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -9,6 +9,7 @@ from esphome.config import iter_component_configs, iter_components from esphome.const import ( ENV_NOGITIGNORE, HEADER_FILE_EXTENSIONS, + PLATFORM_ESP32, SOURCE_FILE_EXTENSIONS, __version__, ) @@ -107,7 +108,10 @@ def storage_should_clean(old: StorageJSON, new: StorageJSON) -> bool: if old.build_path != new.build_path: return True if old.loaded_integrations != new.loaded_integrations: - return True + if new.core_platform == PLATFORM_ESP32: + from esphome.components.esp32 import FRAMEWORK_ESP_IDF + + return new.framework == FRAMEWORK_ESP_IDF return False From 91c7c436827ef7c538b8ee1be74dd00a2179f2d6 Mon Sep 17 00:00:00 2001 From: Mathieu Rene Date: Mon, 2 Sep 2024 17:26:10 -0400 Subject: [PATCH 32/55] Fix build for esp32h2 using esp-idf 5.3 (#7393) --- esphome/components/debug/debug_esp32.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp index cfdfdd2a61..34aea9e26b 100644 --- a/esphome/components/debug/debug_esp32.cpp +++ b/esphome/components/debug/debug_esp32.cpp @@ -16,6 +16,8 @@ #include #elif defined(USE_ESP32_VARIANT_ESP32S3) #include +#elif defined(USE_ESP32_VARIANT_ESP32H2) +#include #endif #ifdef USE_ARDUINO #include @@ -61,7 +63,7 @@ std::string DebugComponent::get_reset_reason_() { case RTCWDT_SYS_RESET: reset_reason = "RTC Watch Dog Reset Digital Core"; break; -#if !defined(USE_ESP32_VARIANT_ESP32C6) +#if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2) case INTRUSION_RESET: reset_reason = "Intrusion Reset CPU"; break; From 816b060edcdf5c579b7df0c5a7fecf329c48759d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Aug 2024 05:07:31 +1200 Subject: [PATCH 33/55] [datetime] Fix templated args (#7368) --- esphome/components/datetime/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/datetime/__init__.py b/esphome/components/datetime/__init__.py index 4fda97c5bc..5429121d56 100644 --- a/esphome/components/datetime/__init__.py +++ b/esphome/components/datetime/__init__.py @@ -186,7 +186,7 @@ async def datetime_date_set_to_code(config, action_id, template_arg, args): date_config = config[CONF_DATE] if cg.is_template(date_config): - template_ = await cg.templatable(date_config, [], cg.ESPTime) + template_ = await cg.templatable(date_config, args, cg.ESPTime) cg.add(action_var.set_date(template_)) else: date_struct = cg.StructInitializer( @@ -217,7 +217,7 @@ async def datetime_time_set_to_code(config, action_id, template_arg, args): time_config = config[CONF_TIME] if cg.is_template(time_config): - template_ = await cg.templatable(time_config, [], cg.ESPTime) + template_ = await cg.templatable(time_config, args, cg.ESPTime) cg.add(action_var.set_time(template_)) else: time_struct = cg.StructInitializer( @@ -248,7 +248,7 @@ async def datetime_datetime_set_to_code(config, action_id, template_arg, args): datetime_config = config[CONF_DATETIME] if cg.is_template(datetime_config): - template_ = await cg.templatable(datetime_config, [], cg.ESPTime) + template_ = await cg.templatable(datetime_config, args, cg.ESPTime) cg.add(action_var.set_datetime(template_)) else: datetime_struct = cg.StructInitializer( From 04ec6c5677201b47e74049509487e61b13bef246 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Mon, 2 Sep 2024 04:32:34 +0200 Subject: [PATCH 34/55] Enable IPv6 when manual IPv4 is enabled (#7381) --- esphome/components/ethernet/ethernet_component.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 962a864a29..fdb6eb2da0 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -472,13 +472,13 @@ void EthernetComponent::start_connect_() { if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { ESPHL_ERROR_CHECK(err, "DHCPC start error"); } -#if USE_NETWORK_IPV6 - err = esp_netif_create_ip6_linklocal(this->eth_netif_); - if (err != ESP_OK) { - ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed"); - } -#endif /* USE_NETWORK_IPV6 */ } +#if USE_NETWORK_IPV6 + err = esp_netif_create_ip6_linklocal(this->eth_netif_); + if (err != ESP_OK) { + ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed"); + } +#endif /* USE_NETWORK_IPV6 */ this->connect_begin_ = millis(); this->status_set_warning(); From c9c5ca28d20f68f6412c0997c345217da2f971fd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 2 Sep 2024 19:11:21 +1200 Subject: [PATCH 35/55] [core] Only clean build files with esp-idf (#7388) --- esphome/storage_json.py | 16 ++++++++++++++++ esphome/writer.py | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/esphome/storage_json.py b/esphome/storage_json.py index e2e7514904..2d12ee01a0 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -48,6 +48,8 @@ class StorageJSON: firmware_bin_path: str, loaded_integrations: set[str], no_mdns: bool, + framework: str | None = None, + core_platform: str | None = None, ) -> None: # Version of the storage JSON schema assert storage_version is None or isinstance(storage_version, int) @@ -78,6 +80,10 @@ class StorageJSON: self.loaded_integrations = loaded_integrations # Is mDNS disabled self.no_mdns = no_mdns + # The framework used to compile the firmware + self.framework = framework + # The core platform of this firmware. Like "esp32", "rp2040", "host" etc. + self.core_platform = core_platform def as_dict(self): return { @@ -94,6 +100,8 @@ class StorageJSON: "firmware_bin_path": self.firmware_bin_path, "loaded_integrations": sorted(self.loaded_integrations), "no_mdns": self.no_mdns, + "framework": self.framework, + "core_platform": self.core_platform, } def to_json(self): @@ -127,6 +135,8 @@ class StorageJSON: and CONF_DISABLED in esph.config[CONF_MDNS] and esph.config[CONF_MDNS][CONF_DISABLED] is True ), + framework=esph.target_framework, + core_platform=esph.target_platform, ) @staticmethod @@ -147,6 +157,8 @@ class StorageJSON: firmware_bin_path=None, loaded_integrations=set(), no_mdns=False, + framework=None, + core_platform=platform.lower(), ) @staticmethod @@ -168,6 +180,8 @@ class StorageJSON: firmware_bin_path = storage.get("firmware_bin_path") loaded_integrations = set(storage.get("loaded_integrations", [])) no_mdns = storage.get("no_mdns", False) + framework = storage.get("framework") + core_platform = storage.get("core_platform") return StorageJSON( storage_version, name, @@ -182,6 +196,8 @@ class StorageJSON: firmware_bin_path, loaded_integrations, no_mdns, + framework, + core_platform, ) @staticmethod diff --git a/esphome/writer.py b/esphome/writer.py index 57435d3463..79ee72996c 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -9,6 +9,7 @@ from esphome.config import iter_component_configs, iter_components from esphome.const import ( ENV_NOGITIGNORE, HEADER_FILE_EXTENSIONS, + PLATFORM_ESP32, SOURCE_FILE_EXTENSIONS, __version__, ) @@ -107,7 +108,10 @@ def storage_should_clean(old: StorageJSON, new: StorageJSON) -> bool: if old.build_path != new.build_path: return True if old.loaded_integrations != new.loaded_integrations: - return True + if new.core_platform == PLATFORM_ESP32: + from esphome.components.esp32 import FRAMEWORK_ESP_IDF + + return new.framework == FRAMEWORK_ESP_IDF return False From e5e06a12ef22aeec591880ec53de572bad1f8a3b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 3 Sep 2024 09:57:28 +1200 Subject: [PATCH 36/55] Bump version to 2024.8.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index b27949bf29..00f2d6a13b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.8.1" +__version__ = "2024.8.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 01c50432c91b871a91a403778250d5b21121cd2a Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Tue, 3 Sep 2024 00:16:59 +0200 Subject: [PATCH 37/55] Bump mDNS and follow ruff's suggestions (#7308) --- esphome/components/mdns/__init__.py | 12 ++++++------ esphome/idf_component.yml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index fb90986314..dd68fbb93c 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -1,17 +1,17 @@ +import esphome.codegen as cg +from esphome.components.esp32 import add_idf_component +import esphome.config_validation as cv from esphome.const import ( + CONF_DISABLED, CONF_ID, CONF_PORT, CONF_PROTOCOL, - CONF_SERVICES, CONF_SERVICE, + CONF_SERVICES, KEY_CORE, KEY_FRAMEWORK_VERSION, - CONF_DISABLED, ) -import esphome.codegen as cg -import esphome.config_validation as cv from esphome.core import CORE, coroutine_with_priority -from esphome.components.esp32 import add_idf_component CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] @@ -91,7 +91,7 @@ async def to_code(config): add_idf_component( name="mdns", repo="https://github.com/espressif/esp-protocols.git", - ref="mdns-v1.2.5", + ref="mdns-v1.3.2", path="components/mdns", ) diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 5f4701b5a3..c79ba1b0ed 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -7,7 +7,7 @@ dependencies: version: v2.0.9 mdns: git: https://github.com/espressif/esp-protocols.git - version: mdns-v1.2.5 + version: mdns-v1.3.2 path: components/mdns rules: - if: "idf_version >=5.0" From 29f0b504b9b5944d6b95d135d8ef973c57056604 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Tue, 3 Sep 2024 00:28:18 +0200 Subject: [PATCH 38/55] Bump rp2040 Arduino platform and framework (#7134) --- esphome/components/rp2040/__init__.py | 15 +++++++-------- platformio.ini | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index f5c3b8bda2..8f1d26f780 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -1,6 +1,5 @@ import logging import os - from string import ascii_letters, digits import esphome.codegen as cg @@ -8,6 +7,7 @@ import esphome.config_validation as cv from esphome.const import ( CONF_BOARD, CONF_FRAMEWORK, + CONF_PLATFORM_VERSION, CONF_SOURCE, CONF_VERSION, KEY_CORE, @@ -15,10 +15,9 @@ from esphome.const import ( KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, PLATFORM_RP2040, - CONF_PLATFORM_VERSION, ) -from esphome.core import CORE, coroutine_with_priority, EsphomeError -from esphome.helpers import mkdir_p, write_file, copy_file_if_changed +from esphome.core import CORE, EsphomeError, coroutine_with_priority +from esphome.helpers import copy_file_if_changed, mkdir_p, write_file from .const import KEY_BOARD, KEY_PIO_FILES, KEY_RP2040, rp2040_ns @@ -81,19 +80,19 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: # The default/recommended arduino framework version # - https://github.com/earlephilhower/arduino-pico/releases # - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico -RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 7, 2) +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 9, 4) # The platformio/raspberrypi version to use for arduino frameworks # - https://github.com/platformio/platform-raspberrypi/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi -ARDUINO_PLATFORM_VERSION = cv.Version(1, 12, 0) +ARDUINO_PLATFORM_VERSION = cv.Version(1, 13, 0) def _arduino_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(3, 4, 0), "https://github.com/earlephilhower/arduino-pico"), - "latest": (cv.Version(3, 4, 0), None), + "dev": (cv.Version(3, 9, 4), "https://github.com/earlephilhower/arduino-pico"), + "latest": (cv.Version(3, 9, 4), None), "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } diff --git a/platformio.ini b/platformio.ini index 147159a841..ee18068a29 100644 --- a/platformio.ini +++ b/platformio.ini @@ -168,7 +168,7 @@ board_build.filesystem_size = 0.5m platform = https://github.com/maxgerhardt/platform-raspberrypi.git platform_packages = ; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted - earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.7.2/rp2040-3.7.2.zip + earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.9.4/rp2040-3.9.4.zip framework = arduino lib_deps = From 3b14b0efce1e9be58f5da6364713ff3d7a12d06d Mon Sep 17 00:00:00 2001 From: Dan Greco <21044725+dangreco@users.noreply.github.com> Date: Mon, 2 Sep 2024 18:35:54 -0400 Subject: [PATCH 39/55] [gree] Add support for YX1FF remote (#7298) --- esphome/components/gree/climate.py | 3 +- esphome/components/gree/gree.cpp | 57 ++++++++++++++++++++++++++++-- esphome/components/gree/gree.h | 12 +++++-- 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/esphome/components/gree/climate.py b/esphome/components/gree/climate.py index c88a428391..75436f2cf5 100644 --- a/esphome/components/gree/climate.py +++ b/esphome/components/gree/climate.py @@ -1,6 +1,6 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import climate_ir +import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_MODEL CODEOWNERS = ["@orestismers"] @@ -17,6 +17,7 @@ MODELS = { "yaa": Model.GREE_YAA, "yac": Model.GREE_YAC, "yac1fb9": Model.GREE_YAC1FB9, + "yx1ff": Model.GREE_YX1FF, } CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( diff --git a/esphome/components/gree/gree.cpp b/esphome/components/gree/gree.cpp index cce2a8ffee..6d179a947b 100644 --- a/esphome/components/gree/gree.cpp +++ b/esphome/components/gree/gree.cpp @@ -6,7 +6,15 @@ namespace gree { static const char *const TAG = "gree.climate"; -void GreeClimate::set_model(Model model) { this->model_ = model; } +void GreeClimate::set_model(Model model) { + if (model == GREE_YX1FF) { + this->fan_modes_.insert(climate::CLIMATE_FAN_QUIET); // YX1FF 4 speed + this->presets_.insert(climate::CLIMATE_PRESET_NONE); // YX1FF sleep mode + this->presets_.insert(climate::CLIMATE_PRESET_SLEEP); // YX1FF sleep mode + } + + this->model_ = model; +} void GreeClimate::transmit_state() { uint8_t remote_state[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00}; @@ -14,7 +22,7 @@ void GreeClimate::transmit_state() { remote_state[0] = this->fan_speed_() | this->operation_mode_(); remote_state[1] = this->temperature_(); - if (this->model_ == GREE_YAN) { + if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) { remote_state[2] = 0x60; remote_state[3] = 0x50; remote_state[4] = this->vertical_swing_(); @@ -36,8 +44,18 @@ void GreeClimate::transmit_state() { } } + if (this->model_ == GREE_YX1FF) { + if (this->fan_speed_() == GREE_FAN_TURBO) { + remote_state[2] |= GREE_FAN_TURBO_BIT; + } + + if (this->preset_() == GREE_PRESET_SLEEP) { + remote_state[0] |= GREE_PRESET_SLEEP_BIT; + } + } + // Calculate the checksum - if (this->model_ == GREE_YAN) { + if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) { remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0); } else { remote_state[7] = @@ -124,6 +142,23 @@ uint8_t GreeClimate::operation_mode_() { } uint8_t GreeClimate::fan_speed_() { + // YX1FF has 4 fan speeds -- we treat low as quiet and turbo as high + if (this->model_ == GREE_YX1FF) { + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_QUIET: + return GREE_FAN_1; + case climate::CLIMATE_FAN_LOW: + return GREE_FAN_2; + case climate::CLIMATE_FAN_MEDIUM: + return GREE_FAN_3; + case climate::CLIMATE_FAN_HIGH: + return GREE_FAN_TURBO; + case climate::CLIMATE_FAN_AUTO: + default: + return GREE_FAN_AUTO; + } + } + switch (this->fan_mode.value()) { case climate::CLIMATE_FAN_LOW: return GREE_FAN_1; @@ -161,5 +196,21 @@ uint8_t GreeClimate::temperature_() { return (uint8_t) roundf(clamp(this->target_temperature, GREE_TEMP_MIN, GREE_TEMP_MAX)); } +uint8_t GreeClimate::preset_() { + // YX1FF has sleep preset + if (this->model_ == GREE_YX1FF) { + switch (this->preset.value()) { + case climate::CLIMATE_PRESET_NONE: + return GREE_PRESET_NONE; + case climate::CLIMATE_PRESET_SLEEP: + return GREE_PRESET_SLEEP; + default: + return GREE_PRESET_NONE; + } + } + + return GREE_PRESET_NONE; +} + } // namespace gree } // namespace esphome diff --git a/esphome/components/gree/gree.h b/esphome/components/gree/gree.h index 524a95aebd..6762b41eb0 100644 --- a/esphome/components/gree/gree.h +++ b/esphome/components/gree/gree.h @@ -25,7 +25,6 @@ const uint8_t GREE_FAN_AUTO = 0x00; const uint8_t GREE_FAN_1 = 0x10; const uint8_t GREE_FAN_2 = 0x20; const uint8_t GREE_FAN_3 = 0x30; -const uint8_t GREE_FAN_TURBO = 0x80; // IR Transmission const uint32_t GREE_IR_FREQUENCY = 38000; @@ -70,8 +69,16 @@ const uint8_t GREE_HDIR_MIDDLE = 0x04; const uint8_t GREE_HDIR_MRIGHT = 0x05; const uint8_t GREE_HDIR_RIGHT = 0x06; +// Only available on YX1FF +// Turbo (high) fan mode + sleep preset mode +const uint8_t GREE_FAN_TURBO = 0x80; +const uint8_t GREE_FAN_TURBO_BIT = 0x10; +const uint8_t GREE_PRESET_NONE = 0x00; +const uint8_t GREE_PRESET_SLEEP = 0x01; +const uint8_t GREE_PRESET_SLEEP_BIT = 0x80; + // Model codes -enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9 }; +enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF }; class GreeClimate : public climate_ir::ClimateIR { public: @@ -93,6 +100,7 @@ class GreeClimate : public climate_ir::ClimateIR { uint8_t horizontal_swing_(); uint8_t vertical_swing_(); uint8_t temperature_(); + uint8_t preset_(); Model model_{}; }; From d6eeac06199e562d2d25deaf8bd7cfe1ec90605f Mon Sep 17 00:00:00 2001 From: Tercio Filho Date: Mon, 2 Sep 2024 20:56:19 -0300 Subject: [PATCH 40/55] [modbus_controller] Allow duplicate command config (#7311) --- .../components/modbus_controller/__init__.py | 3 +++ esphome/components/modbus_controller/const.py | 1 + .../modbus_controller/modbus_controller.cpp | 22 ++++++++++--------- .../modbus_controller/modbus_controller.h | 8 +++++++ .../modbus_controller/test.esp32-ard.yaml | 1 + .../modbus_controller/test.esp32-idf.yaml | 1 + 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 1d0f406783..8146124c28 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -13,6 +13,7 @@ from esphome.const import ( ) from esphome.cpp_helpers import logging from .const import ( + CONF_ALLOW_DUPLICATE_COMMANDS, CONF_BITMASK, CONF_BYTE_OFFSET, CONF_COMMAND_THROTTLE, @@ -126,6 +127,7 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(ModbusController), + cv.Optional(CONF_ALLOW_DUPLICATE_COMMANDS, default=False): cv.boolean, cv.Optional( CONF_COMMAND_THROTTLE, default="0ms" ): cv.positive_time_period_milliseconds, @@ -253,6 +255,7 @@ async def add_modbus_base_properties( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_allow_duplicate_commands(config[CONF_ALLOW_DUPLICATE_COMMANDS])) cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE])) cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES])) if CONF_SERVER_REGISTERS in config: diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index 1f5c39895c..5d9a61dee7 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -1,3 +1,4 @@ +CONF_ALLOW_DUPLICATE_COMMANDS = "allow_duplicate_commands" CONF_BITMASK = "bitmask" CONF_BYTE_OFFSET = "byte_offset" CONF_COMMAND_THROTTLE = "command_throttle" diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 378e5c06c0..8f48847a4f 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -175,16 +175,18 @@ void ModbusController::on_register_data(ModbusRegisterType register_type, uint16 } void ModbusController::queue_command(const ModbusCommandItem &command) { - // check if this command is already qeued. - // not very effective but the queue is never really large - for (auto &item : command_queue_) { - if (item->is_equal(command)) { - ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u", - static_cast(command.register_type), command.register_address, command.register_count); - // update the payload of the queued command - // replaces a previous command - item->payload = command.payload; - return; + if (!this->allow_duplicate_commands_) { + // check if this command is already qeued. + // not very effective but the queue is never really large + for (auto &item : command_queue_) { + if (item->is_equal(command)) { + ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u", + static_cast(command.register_type), command.register_address, command.register_count); + // update the payload of the queued command + // replaces a previous command + item->payload = command.payload; + return; + } } } command_queue_.push_back(make_unique(command)); diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 3bc11da879..e88f4c07f7 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -448,6 +448,12 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { /// incoming queue void on_write_register_response(ModbusRegisterType register_type, uint16_t start_address, const std::vector &data); + /// Allow a duplicate command to be sent + void set_allow_duplicate_commands(bool allow_duplicate_commands) { + this->allow_duplicate_commands_ = allow_duplicate_commands; + } + /// get if a duplicate command can be sent + bool get_allow_duplicate_commands() { return this->allow_duplicate_commands_; } /// called by esphome generated code to set the command_throttle period void set_command_throttle(uint16_t command_throttle) { this->command_throttle_ = command_throttle; } /// called by esphome generated code to set the offline_skip_updates @@ -482,6 +488,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { std::list> command_queue_; /// modbus response data waiting to get processed std::queue> incoming_queue_; + /// if duplicate commands can be sent + bool allow_duplicate_commands_; /// when was the last send operation uint32_t last_command_timestamp_; /// min time in ms between sending modbus commands diff --git a/tests/components/modbus_controller/test.esp32-ard.yaml b/tests/components/modbus_controller/test.esp32-ard.yaml index 3e022b10ab..b6e38aeb9c 100644 --- a/tests/components/modbus_controller/test.esp32-ard.yaml +++ b/tests/components/modbus_controller/test.esp32-ard.yaml @@ -20,6 +20,7 @@ modbus_controller: - id: modbus_controller1 address: 0x2 modbus_id: mod_bus1 + allow_duplicate_commands: false - id: modbus_controller2 address: 0x2 modbus_id: mod_bus2 diff --git a/tests/components/modbus_controller/test.esp32-idf.yaml b/tests/components/modbus_controller/test.esp32-idf.yaml index c5fe3fd057..d5407ac406 100644 --- a/tests/components/modbus_controller/test.esp32-idf.yaml +++ b/tests/components/modbus_controller/test.esp32-idf.yaml @@ -12,3 +12,4 @@ modbus_controller: - id: modbus_controller1 address: 0x2 modbus_id: mod_bus1 + allow_duplicate_commands: true From f8ec5242c98649cb590e24667dd3791a5f6b6df1 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 2 Sep 2024 20:47:54 -0400 Subject: [PATCH 41/55] Better support for task blocking ring buffer reads and writes (#7390) --- esphome/core/ring_buffer.cpp | 15 ++++++++-- esphome/core/ring_buffer.h | 56 ++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/esphome/core/ring_buffer.cpp b/esphome/core/ring_buffer.cpp index 9bd3d9d853..d8ca831de0 100644 --- a/esphome/core/ring_buffer.cpp +++ b/esphome/core/ring_buffer.cpp @@ -20,13 +20,20 @@ std::unique_ptr RingBuffer::create(size_t len) { return nullptr; } - rb->handle_ = xStreamBufferCreateStatic(len + 1, 0, rb->storage_, &rb->structure_); + rb->handle_ = xStreamBufferCreateStatic(len + 1, 1, rb->storage_, &rb->structure_); ESP_LOGD(TAG, "Created ring buffer with size %u", len); return rb; } size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) { - return xStreamBufferReceive(this->handle_, data, len, ticks_to_wait); + if (ticks_to_wait > 0) + xStreamBufferSetTriggerLevel(this->handle_, len); + + size_t bytes_read = xStreamBufferReceive(this->handle_, data, len, ticks_to_wait); + + xStreamBufferSetTriggerLevel(this->handle_, 1); + + return bytes_read; } size_t RingBuffer::write(void *data, size_t len) { @@ -39,6 +46,10 @@ size_t RingBuffer::write(void *data, size_t len) { return xStreamBufferSend(this->handle_, data, len, 0); } +size_t RingBuffer::write_without_replacement(void *data, size_t len, TickType_t ticks_to_wait) { + return xStreamBufferSend(this->handle_, data, len, ticks_to_wait); +} + size_t RingBuffer::available() const { return xStreamBufferBytesAvailable(this->handle_); } size_t RingBuffer::free() const { return xStreamBufferSpacesAvailable(this->handle_); } diff --git a/esphome/core/ring_buffer.h b/esphome/core/ring_buffer.h index e602068844..97ffefcefa 100644 --- a/esphome/core/ring_buffer.h +++ b/esphome/core/ring_buffer.h @@ -12,13 +12,69 @@ namespace esphome { class RingBuffer { public: + /** + * @brief Reads from the ring buffer, waiting up to a specified number of ticks if necessary. + * + * Available bytes are read into the provided data pointer. If not enough bytes are available, + * the function will wait up to `ticks_to_wait` FreeRTOS ticks before reading what is available. + * + * @param data Pointer to copy read data into + * @param len Number of bytes to read + * @param ticks_to_wait Maximum number of FreeRTOS ticks to wait (default: 0) + * @return Number of bytes read + */ size_t read(void *data, size_t len, TickType_t ticks_to_wait = 0); + /** + * @brief Writes to the ring buffer, overwriting oldest data if necessary. + * + * The provided data is written to the ring buffer. If not enough space is available, + * the function will overwrite the oldest data in the ring buffer. + * + * @param data Pointer to data for writing + * @param len Number of bytes to write + * @return Number of bytes written + */ size_t write(void *data, size_t len); + /** + * @brief Writes to the ring buffer without overwriting oldest data. + * + * The provided data is written to the ring buffer. If not enough space is available, + * the function will wait up to `ticks_to_wait` FreeRTOS ticks before writing as much as possible. + * + * @param data Pointer to data for writing + * @param len Number of bytes to write + * @param ticks_to_wait Maximum number of FreeRTOS ticks to wait (default: 0) + * @return Number of bytes written + */ + size_t write_without_replacement(void *data, size_t len, TickType_t ticks_to_wait = 0); + + /** + * @brief Returns the number of available bytes in the ring buffer. + * + * This function provides the number of bytes that can be read from the ring buffer + * without blocking the calling FreeRTOS task. + * + * @return Number of available bytes + */ size_t available() const; + + /** + * @brief Returns the number of free bytes in the ring buffer. + * + * This function provides the number of bytes that can be written to the ring buffer + * without overwriting data or blocking the calling FreeRTOS task. + * + * @return Number of free bytes + */ size_t free() const; + /** + * @brief Resets the ring buffer, discarding all stored data. + * + * @return pdPASS if successful, pdFAIL otherwise + */ BaseType_t reset(); static std::unique_ptr create(size_t len); From 39b2f30b16d263864a9d5fb8444d4814d8658cc4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 2 Sep 2024 10:36:18 +1200 Subject: [PATCH 42/55] Bump Dockerfile dependencies (#7386) --- docker/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 16f37274c6..4393d5a447 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -34,8 +34,8 @@ RUN \ python3-wheel=0.38.4-2 \ iputils-ping=3:20221126-1 \ git=1:2.39.2-1.1 \ - curl=7.88.1-10+deb12u6 \ - openssh-client=1:9.2p1-2+deb12u2 \ + curl=7.88.1-10+deb12u7 \ + openssh-client=1:9.2p1-2+deb12u3 \ python3-cffi=1.15.1-5 \ libcairo2=1.16.0-7 \ libmagic1=1:5.44-3 \ @@ -49,7 +49,7 @@ RUN \ zlib1g-dev=1:1.2.13.dfsg-1 \ libjpeg-dev=1:2.1.5-2 \ libfreetype-dev=2.12.1+dfsg-5+deb12u3 \ - libssl-dev=3.0.13-1~deb12u1 \ + libssl-dev=3.0.14-1~deb12u1 \ libffi-dev=3.4.4-1 \ libopenjp2-7=2.5.0-2 \ libtiff6=4.5.0-6+deb12u1 \ From cb4bede6d8790937e287cd69649c81871b1ac6b4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:06:54 +1200 Subject: [PATCH 43/55] Bump version to 2024.8.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 00f2d6a13b..05be1877b3 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.8.2" +__version__ = "2024.8.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From c6e64a9ed344dc8d34071fd3cfaefa9cd47d1f92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 19:22:56 +1200 Subject: [PATCH 44/55] Bump pypa/gh-action-pypi-publish from 1.9.0 to 1.10.0 (#7395) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7895e7624a..9e932a3dfc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,7 @@ jobs: pip3 install build python3 -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.9.0 + uses: pypa/gh-action-pypi-publish@v1.10.0 deploy-docker: name: Build ESPHome ${{ matrix.platform }} From 10ccc5f12584c498c179974877a6b15f327066d7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:55:41 +1200 Subject: [PATCH 45/55] [api] Remove id from ``MediaPlayerSupportedFormat`` (#7406) --- esphome/components/api/api.proto | 1 - esphome/components/api/api_pb2_service.cpp | 19 ------------------- esphome/components/api/api_pb2_service.h | 4 ---- 3 files changed, 24 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 84183357dc..ad6fc79cf3 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1112,7 +1112,6 @@ enum MediaPlayerFormatPurpose { MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1; } message MediaPlayerSupportedFormat { - option (id) = 119; option (ifdef) = "USE_MEDIA_PLAYER"; string format = 1; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 16c0e5654f..269a755e9e 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -311,14 +311,6 @@ bool APIServerConnectionBase::send_list_entities_button_response(const ListEntit #ifdef USE_BUTTON #endif #ifdef USE_MEDIA_PLAYER -bool APIServerConnectionBase::send_media_player_supported_format(const MediaPlayerSupportedFormat &msg) { -#ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "send_media_player_supported_format: %s", msg.dump().c_str()); -#endif - return this->send_message_(msg, 119); -} -#endif -#ifdef USE_MEDIA_PLAYER bool APIServerConnectionBase::send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg) { #ifdef HAS_PROTO_MESSAGE_DUMP ESP_LOGVV(TAG, "send_list_entities_media_player_response: %s", msg.dump().c_str()); @@ -1143,17 +1135,6 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str()); #endif this->on_update_command_request(msg); -#endif - break; - } - case 119: { -#ifdef USE_MEDIA_PLAYER - MediaPlayerSupportedFormat msg; - msg.decode(msg_data, msg_size); -#ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_media_player_supported_format: %s", msg.dump().c_str()); -#endif - this->on_media_player_supported_format(msg); #endif break; } diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 83b5e3a444..83bfc2ed98 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -145,10 +145,6 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_BUTTON virtual void on_button_command_request(const ButtonCommandRequest &value){}; #endif -#ifdef USE_MEDIA_PLAYER - bool send_media_player_supported_format(const MediaPlayerSupportedFormat &msg); - virtual void on_media_player_supported_format(const MediaPlayerSupportedFormat &value){}; -#endif #ifdef USE_MEDIA_PLAYER bool send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg); #endif From 1a71cc304740f7823ca77eb3bbab7128f7dd612a Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Wed, 4 Sep 2024 04:02:33 +0200 Subject: [PATCH 46/55] Drop max BLE client connections limitation (#7088) --- esphome/components/ble_client/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 6bf4ff739e..bc7d517695 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -65,9 +65,7 @@ CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification" CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" CONF_AUTO_CONNECT = "auto_connect" -# Espressif platformio framework is built with MAX_BLE_CONN to 3, so -# enforce this in yaml checks. -MULTI_CONF = 3 +MULTI_CONF = True CONFIG_SCHEMA = ( cv.Schema( From 188faa6530cd7d2a2f6a5a24eca6db1fcc9943f3 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Wed, 4 Sep 2024 04:38:47 +0100 Subject: [PATCH 47/55] [bl0942] loop and overflow cleanup (#7358) --- esphome/components/bl0942/bl0942.cpp | 32 ++++++++++++++++++++++------ esphome/components/bl0942/bl0942.h | 2 ++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/esphome/components/bl0942/bl0942.cpp b/esphome/components/bl0942/bl0942.cpp index 606d3629da..c70b5f1775 100644 --- a/esphome/components/bl0942/bl0942.cpp +++ b/esphome/components/bl0942/bl0942.cpp @@ -41,20 +41,33 @@ static const uint32_t BL0942_REG_MODE_DEFAULT = static const uint32_t BL0942_REG_SOFT_RESET_MAGIC = 0x5a5a5a; static const uint32_t BL0942_REG_USR_WRPROT_MAGIC = 0x55; +// 23-byte packet, 11 bits per byte, 2400 baud: about 105ms +static const uint32_t PKT_TIMEOUT_MS = 200; + void BL0942::loop() { DataPacket buffer; - if (!this->available()) { + int avail = this->available(); + + if (!avail) { return; } + if (avail < sizeof(buffer)) { + if (!this->rx_start_) { + this->rx_start_ = millis(); + } else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) { + ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%d bytes)", avail); + this->read_array((uint8_t *) &buffer, avail); + this->rx_start_ = 0; + } + return; + } + if (this->read_array((uint8_t *) &buffer, sizeof(buffer))) { if (this->validate_checksum_(&buffer)) { this->received_package_(&buffer); } - } else { - ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); - while (read() >= 0) - ; } + this->rx_start_ = 0; } bool BL0942::validate_checksum_(DataPacket *data) { @@ -133,10 +146,17 @@ void BL0942::received_package_(DataPacket *data) { return; } + // cf_cnt is only 24 bits, so track overflows + uint32_t cf_cnt = (uint24_t) data->cf_cnt; + cf_cnt |= this->prev_cf_cnt_ & 0xff000000; + if (cf_cnt < this->prev_cf_cnt_) { + cf_cnt += 0x1000000; + } + this->prev_cf_cnt_ = cf_cnt; + float v_rms = (uint24_t) data->v_rms / voltage_reference_; float i_rms = (uint24_t) data->i_rms / current_reference_; float watt = (int24_t) data->watt / power_reference_; - uint32_t cf_cnt = (uint24_t) data->cf_cnt; float total_energy_consumption = cf_cnt / energy_reference_; float frequency = 1000000.0f / data->frequency; diff --git a/esphome/components/bl0942/bl0942.h b/esphome/components/bl0942/bl0942.h index 52347c1bc3..a5e48bdf1d 100644 --- a/esphome/components/bl0942/bl0942.h +++ b/esphome/components/bl0942/bl0942.h @@ -67,6 +67,8 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { float energy_reference_ = BL0942_EREF; uint8_t address_ = 0; LineFrequency line_freq_ = LINE_FREQUENCY_50HZ; + uint32_t rx_start_ = 0; + uint32_t prev_cf_cnt_ = 0; bool validate_checksum_(DataPacket *data); int read_reg_(uint8_t reg); From a96de54d46f54a25a15eb889abeea4e53bb3d18f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:45:40 +1200 Subject: [PATCH 48/55] Bump peter-evans/create-pull-request from 6.1.0 to 7.0.0 (#7405) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/sync-device-classes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 7677425236..e834ff3793 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -36,7 +36,7 @@ jobs: python ./script/sync-device_class.py - name: Commit changes - uses: peter-evans/create-pull-request@v6.1.0 + uses: peter-evans/create-pull-request@v7.0.0 with: commit-message: "Synchronise Device Classes from Home Assistant" committer: esphomebot From a7fd3b34aae0b9465a6bea66c00b2746d8cfea12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:47:59 +1200 Subject: [PATCH 49/55] Bump pypa/gh-action-pypi-publish from 1.10.0 to 1.10.1 (#7404) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e932a3dfc..522de63360 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,7 @@ jobs: pip3 install build python3 -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.10.0 + uses: pypa/gh-action-pypi-publish@v1.10.1 deploy-docker: name: Build ESPHome ${{ matrix.platform }} From e882cea47e76c50db674baf8dae0fa8744aabc4c Mon Sep 17 00:00:00 2001 From: Jeff Cooper Date: Tue, 3 Sep 2024 23:48:13 -0400 Subject: [PATCH 50/55] Voice assist improvement - configurable conversation_id timeout (#7385) --- esphome/components/voice_assistant/__init__.py | 6 ++++++ esphome/components/voice_assistant/voice_assistant.cpp | 8 +++++++- esphome/components/voice_assistant/voice_assistant.h | 3 +++ tests/components/voice_assistant/test.esp32-ard.yaml | 1 + 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index 031edbf27a..a4fb572208 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -43,6 +43,8 @@ CONF_VOLUME_MULTIPLIER = "volume_multiplier" CONF_WAKE_WORD = "wake_word" +CONF_CONVERSATION_TIMEOUT = "conversation_timeout" + CONF_ON_TIMER_STARTED = "on_timer_started" CONF_ON_TIMER_UPDATED = "on_timer_updated" CONF_ON_TIMER_CANCELLED = "on_timer_cancelled" @@ -100,6 +102,9 @@ CONFIG_SCHEMA = cv.All( cv.float_with_unit("decibel full scale", "(dBFS|dbfs|DBFS)"), cv.int_range(0, 31), ), + cv.Optional( + CONF_CONVERSATION_TIMEOUT, default="300s" + ): cv.positive_time_period_milliseconds, cv.Optional(CONF_VOLUME_MULTIPLIER, default=1.0): cv.float_range( min=0.0, min_included=False ), @@ -182,6 +187,7 @@ async def to_code(config): cg.add(var.set_noise_suppression_level(config[CONF_NOISE_SUPPRESSION_LEVEL])) cg.add(var.set_auto_gain(config[CONF_AUTO_GAIN])) cg.add(var.set_volume_multiplier(config[CONF_VOLUME_MULTIPLIER])) + cg.add(var.set_conversation_timeout(config[CONF_CONVERSATION_TIMEOUT])) if CONF_ON_LISTENING in config: await automation.build_automation( diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index e4f388db68..43c7428858 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -171,6 +171,11 @@ void VoiceAssistant::deallocate_buffers_() { #endif } +void VoiceAssistant::reset_conversation_id() { + this->conversation_id_ = ""; + ESP_LOGD(TAG, "reset conversation ID"); +} + int VoiceAssistant::read_microphone_() { size_t bytes_read = 0; if (this->mic_->is_running()) { // Read audio into input buffer @@ -299,7 +304,8 @@ void VoiceAssistant::loop() { break; } this->set_state_(State::STARTING_PIPELINE); - this->set_timeout("reset-conversation_id", 5 * 60 * 1000, [this]() { this->conversation_id_ = ""; }); + this->set_timeout("reset-conversation_id", this->conversation_timeout_, + [this]() { this->reset_conversation_id(); }); break; } case State::STARTING_PIPELINE: { diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index a160972e22..88cb0dd413 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -147,6 +147,8 @@ class VoiceAssistant : public Component { } void set_auto_gain(uint8_t auto_gain) { this->auto_gain_ = auto_gain; } void set_volume_multiplier(float volume_multiplier) { this->volume_multiplier_ = volume_multiplier; } + void set_conversation_timeout(uint32_t conversation_timeout) { this->conversation_timeout_ = conversation_timeout; } + void reset_conversation_id(); Trigger<> *get_intent_end_trigger() const { return this->intent_end_trigger_; } Trigger<> *get_intent_start_trigger() const { return this->intent_start_trigger_; } @@ -262,6 +264,7 @@ class VoiceAssistant : public Component { uint8_t noise_suppression_level_; uint8_t auto_gain_; float volume_multiplier_; + uint32_t conversation_timeout_; uint8_t *send_buffer_; int16_t *input_buffer_; diff --git a/tests/components/voice_assistant/test.esp32-ard.yaml b/tests/components/voice_assistant/test.esp32-ard.yaml index 2e0209311d..7f6fd85303 100644 --- a/tests/components/voice_assistant/test.esp32-ard.yaml +++ b/tests/components/voice_assistant/test.esp32-ard.yaml @@ -33,6 +33,7 @@ speaker: voice_assistant: microphone: mic_id_external speaker: speaker_id + conversation_timeout: 60s on_listening: - logger.log: "Voice assistant microphone listening" on_start: From 71a7f6383f66d7e0730a7854d1f5739406310395 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Thu, 5 Sep 2024 01:08:39 +0100 Subject: [PATCH 51/55] Support BL0942 calibration (#7299) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 2 +- esphome/components/bl0942/__init__.py | 2 +- esphome/components/bl0942/bl0942.cpp | 20 +++++- esphome/components/bl0942/bl0942.h | 71 ++++++++++++++++++++ esphome/components/bl0942/sensor.py | 17 +++++ tests/components/bl0942/test.bk72xx-ard.yaml | 4 ++ tests/components/bl0942/test.rp2040-ard.yaml | 2 + 7 files changed, 115 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index ab11086980..8c706fa2d6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -61,7 +61,7 @@ esphome/components/bk72xx/* @kuba2k2 esphome/components/bl0906/* @athom-tech @jesserockz @tarontop esphome/components/bl0939/* @ziceva esphome/components/bl0940/* @tobias- -esphome/components/bl0942/* @dbuezas +esphome/components/bl0942/* @dbuezas @dwmw2 esphome/components/ble_client/* @buxtronix @clydebarrow esphome/components/bluetooth_proxy/* @jesserockz esphome/components/bme280_base/* @esphome/core diff --git a/esphome/components/bl0942/__init__.py b/esphome/components/bl0942/__init__.py index 8ef7857b7b..38b68d84b5 100644 --- a/esphome/components/bl0942/__init__.py +++ b/esphome/components/bl0942/__init__.py @@ -1 +1 @@ -CODEOWNERS = ["@dbuezas"] +CODEOWNERS = ["@dbuezas", "@dwmw2"] diff --git a/esphome/components/bl0942/bl0942.cpp b/esphome/components/bl0942/bl0942.cpp index c70b5f1775..af56e77de6 100644 --- a/esphome/components/bl0942/bl0942.cpp +++ b/esphome/components/bl0942/bl0942.cpp @@ -122,6 +122,20 @@ void BL0942::update() { } void BL0942::setup() { + // If either current or voltage references are set explicitly by the user, + // calculate the power reference from it unless that is also explicitly set. + if ((this->current_reference_set_ || this->voltage_reference_set_) && !this->power_reference_set_) { + this->power_reference_ = (this->voltage_reference_ * this->current_reference_ * 3537.0 / 305978.0) / 73989.0; + this->power_reference_set_ = true; + } + + // Similarly for energy reference, if the power reference was set by the user + // either implicitly or explicitly. + if (this->power_reference_set_ && !this->energy_reference_set_) { + this->energy_reference_ = this->power_reference_ * 3600000 / 419430.4; + this->energy_reference_set_ = true; + } + this->write_reg_(BL0942_REG_USR_WRPROT, BL0942_REG_USR_WRPROT_MAGIC); this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC); @@ -184,11 +198,15 @@ void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexit ESP_LOGCONFIG(TAG, "BL0942:"); ESP_LOGCONFIG(TAG, " Address: %d", this->address_); ESP_LOGCONFIG(TAG, " Nominal line frequency: %d Hz", this->line_freq_); + ESP_LOGCONFIG(TAG, " Current reference: %f", this->current_reference_); + ESP_LOGCONFIG(TAG, " Energy reference: %f", this->energy_reference_); + ESP_LOGCONFIG(TAG, " Power reference: %f", this->power_reference_); + ESP_LOGCONFIG(TAG, " Voltage reference: %f", this->voltage_reference_); LOG_SENSOR("", "Voltage", this->voltage_sensor_); LOG_SENSOR("", "Current", this->current_sensor_); LOG_SENSOR("", "Power", this->power_sensor_); LOG_SENSOR("", "Energy", this->energy_sensor_); - LOG_SENSOR("", "frequency", this->frequency_sensor_); + LOG_SENSOR("", "Frequency", this->frequency_sensor_); } } // namespace bl0942 diff --git a/esphome/components/bl0942/bl0942.h b/esphome/components/bl0942/bl0942.h index a5e48bdf1d..1dc930183f 100644 --- a/esphome/components/bl0942/bl0942.h +++ b/esphome/components/bl0942/bl0942.h @@ -8,6 +8,57 @@ namespace esphome { namespace bl0942 { +// The BL0942 IC is "calibration-free", which means that it doesn't care +// at all about calibration, and that's left to software. It measures a +// voltage differential on its IP/IN pins which linearly proportional to +// the current flow, and another on its VP pin which is proportional to +// the line voltage. It never knows the actual calibration; the values +// it reports are solely in terms of those inputs. +// +// The datasheet refers to the input voltages as I(A) and V(V), both +// in millivolts. It measures them against a reference voltage Vref, +// which is typically 1.218V (but that absolute value is meaningless +// without the actual calibration anyway). +// +// The reported I_RMS value is 305978 I(A)/Vref, and the reported V_RMS +// value is 73989 V(V)/Vref. So we can calibrate those by applying a +// simple meter with a resistive load. +// +// The chip also measures the phase difference between voltage and +// current, and uses it to calculate the power factor (cos φ). It +// reports the WATT value of 3537 * I_RMS * V_RMS * cos φ). +// +// It also integrates total energy based on the WATT value. The time for +// one CF_CNT pulse is 1638.4*256 / WATT. +// +// So... how do we calibrate that? +// +// Using a simple resistive load and an external meter, we can measure +// the true voltage and current for a given V_RMS and I_RMS reading, +// to calculate BL0942_UREF and BL0942_IREF. Those are in units of +// "305978 counts per amp" or "73989 counts per volt" respectively. +// +// We can derive BL0942_PREF from those. Let's eliminate the weird +// factors and express the calibration in plain counts per volt/amp: +// UREF1 = UREF/73989, IREF1 = IREF/305978. +// +// Next... the true power in Watts is V * I * cos φ, so that's equal +// to WATT/3537 * IREF1 * UREF1. Which means +// BL0942_PREF = BL0942_UREF * BL0942_IREF * 3537 / 305978 / 73989. +// +// Finally the accumulated energy. The period of a CF_CNT count is +// 1638.4*256 / WATT seconds, or 419230.4 / WATT seconds. Which means +// the energy represented by a CN_CNT pulse is 419230.4 WATT-seconds. +// Factoring in the calibration, that's 419230.4 / BL0942_PREF actual +// Watt-seconds (or Joules, as the physicists like to call them). +// +// But we're not being physicists today; we we're being engineers, so +// we want to convert to kWh instead. Which we do by dividing by 1000 +// and then by 3600, so the energy in kWh is +// CF_CNT * 419230.4 / BL0942_PREF / 3600000 +// +// Which makes BL0952_EREF = BL0942_PREF * 3600000 / 419430.4 + static const float BL0942_PREF = 596; // taken from tasmota static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218 static const float BL0942_IREF = 251213.46469622; // 305978/1.218 @@ -42,6 +93,22 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } void set_line_freq(LineFrequency freq) { this->line_freq_ = freq; } void set_address(uint8_t address) { this->address_ = address; } + void set_current_reference(float current_ref) { + this->current_reference_ = current_ref; + this->current_reference_set_ = true; + } + void set_energy_reference(float energy_ref) { + this->energy_reference_ = energy_ref; + this->energy_reference_set_ = true; + } + void set_power_reference(float power_ref) { + this->power_reference_ = power_ref; + this->power_reference_set_ = true; + } + void set_voltage_reference(float voltage_ref) { + this->voltage_reference_ = voltage_ref; + this->voltage_reference_set_ = true; + } void loop() override; void update() override; @@ -59,12 +126,16 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { // Divide by this to turn into Watt float power_reference_ = BL0942_PREF; + bool power_reference_set_ = false; // Divide by this to turn into Volt float voltage_reference_ = BL0942_UREF; + bool voltage_reference_set_ = false; // Divide by this to turn into Ampere float current_reference_ = BL0942_IREF; + bool current_reference_set_ = false; // Divide by this to turn into kWh float energy_reference_ = BL0942_EREF; + bool energy_reference_set_ = false; uint8_t address_ = 0; LineFrequency line_freq_ = LINE_FREQUENCY_50HZ; uint32_t rx_start_ = 0; diff --git a/esphome/components/bl0942/sensor.py b/esphome/components/bl0942/sensor.py index c47da45b8c..3574443636 100644 --- a/esphome/components/bl0942/sensor.py +++ b/esphome/components/bl0942/sensor.py @@ -24,6 +24,11 @@ from esphome.const import ( UNIT_WATT, ) +CONF_CURRENT_REFERENCE = "current_reference" +CONF_ENERGY_REFERENCE = "energy_reference" +CONF_POWER_REFERENCE = "power_reference" +CONF_VOLTAGE_REFERENCE = "voltage_reference" + DEPENDENCIES = ["uart"] bl0942_ns = cg.esphome_ns.namespace("bl0942") @@ -77,6 +82,10 @@ CONFIG_SCHEMA = ( ), ), cv.Optional(CONF_ADDRESS, default=0): cv.int_range(min=0, max=3), + cv.Optional(CONF_CURRENT_REFERENCE): cv.float_, + cv.Optional(CONF_ENERGY_REFERENCE): cv.float_, + cv.Optional(CONF_POWER_REFERENCE): cv.float_, + cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_, } ) .extend(cv.polling_component_schema("60s")) @@ -106,3 +115,11 @@ async def to_code(config): cg.add(var.set_frequency_sensor(sens)) cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) cg.add(var.set_address(config[CONF_ADDRESS])) + if (current_reference := config.get(CONF_CURRENT_REFERENCE, None)) is not None: + cg.add(var.set_current_reference(current_reference)) + if (voltage_reference := config.get(CONF_VOLTAGE_REFERENCE, None)) is not None: + cg.add(var.set_voltage_reference(voltage_reference)) + if (power_reference := config.get(CONF_POWER_REFERENCE, None)) is not None: + cg.add(var.set_power_reference(power_reference)) + if (energy_reference := config.get(CONF_ENERGY_REFERENCE, None)) is not None: + cg.add(var.set_energy_reference(energy_reference)) diff --git a/tests/components/bl0942/test.bk72xx-ard.yaml b/tests/components/bl0942/test.bk72xx-ard.yaml index 4ed3eb391d..12772f9375 100644 --- a/tests/components/bl0942/test.bk72xx-ard.yaml +++ b/tests/components/bl0942/test.bk72xx-ard.yaml @@ -20,3 +20,7 @@ sensor: name: BL0942 Energy frequency: name: BL0942 Frequency + voltage_reference: 15968 + current_reference: 124180 + power_reference: 309.1 + energy_reference: 2653 diff --git a/tests/components/bl0942/test.rp2040-ard.yaml b/tests/components/bl0942/test.rp2040-ard.yaml index 8d16efed4f..d07e0c4402 100644 --- a/tests/components/bl0942/test.rp2040-ard.yaml +++ b/tests/components/bl0942/test.rp2040-ard.yaml @@ -18,3 +18,5 @@ sensor: name: BL0942 Energy frequency: name: BL0942 Frequency + voltage_reference: 15968 + current_reference: 124180 From dc4e60526cd63525363cf36be8625fd52840fea8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:49:01 +1200 Subject: [PATCH 52/55] [micro_wake_word] Remove duplicated download code (#7401) --- .../components/micro_wake_word/__init__.py | 27 ++----------------- esphome/external_files.py | 8 +++--- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py index cd45f75b01..a8aa590951 100644 --- a/esphome/components/micro_wake_word/__init__.py +++ b/esphome/components/micro_wake_word/__init__.py @@ -4,8 +4,6 @@ import logging from pathlib import Path from urllib.parse import urljoin -import requests - from esphome import automation, external_files, git from esphome.automation import register_action, register_condition import esphome.codegen as cg @@ -26,7 +24,6 @@ from esphome.const import ( CONF_USERNAME, TYPE_GIT, TYPE_LOCAL, - __version__, ) from esphome.core import CORE, HexInt @@ -179,26 +176,6 @@ def _convert_manifest_v1_to_v2(v1_manifest): return v2_manifest -def _download_file(url: str, path: Path) -> bytes: - if not external_files.has_remote_file_changed(url, path): - _LOGGER.debug("Remote file has not changed, skipping download") - return path.read_bytes() - - try: - req = requests.get( - url, - timeout=external_files.NETWORK_TIMEOUT, - headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"}, - ) - req.raise_for_status() - except requests.exceptions.RequestException as e: - raise cv.Invalid(f"Could not download file from {url}: {e}") from e - - path.parent.mkdir(parents=True, exist_ok=True) - path.write_bytes(req.content) - return req.content - - def _validate_manifest_version(manifest_data): if manifest_version := manifest_data.get(KEY_VERSION): if manifest_version == 1: @@ -223,7 +200,7 @@ def _process_http_source(config): json_path = path / "manifest.json" - json_contents = _download_file(url, json_path) + json_contents = external_files.download_content(url, json_path) manifest_data = json.loads(json_contents) if not isinstance(manifest_data, dict): @@ -234,7 +211,7 @@ def _process_http_source(config): model_path = path / model - _download_file(str(model_url), model_path) + external_files.download_content(str(model_url), model_path) return config diff --git a/esphome/external_files.py b/esphome/external_files.py index baf62286e4..057ff52f3f 100644 --- a/esphome/external_files.py +++ b/esphome/external_files.py @@ -80,10 +80,10 @@ def compute_local_file_dir(domain: str) -> Path: return base_directory -def download_content(url: str, path: Path, timeout=NETWORK_TIMEOUT) -> None: +def download_content(url: str, path: Path, timeout=NETWORK_TIMEOUT) -> bytes: if not has_remote_file_changed(url, path): _LOGGER.debug("Remote file has not changed %s", url) - return + return path.read_bytes() _LOGGER.debug( "Remote file has changed, downloading from %s to %s", @@ -102,4 +102,6 @@ def download_content(url: str, path: Path, timeout=NETWORK_TIMEOUT) -> None: raise cv.Invalid(f"Could not download from {url}: {e}") path.parent.mkdir(parents=True, exist_ok=True) - path.write_bytes(req.content) + data = req.content + path.write_bytes(data) + return data From b4962334251ee8920d46c912588a0adc4bd58201 Mon Sep 17 00:00:00 2001 From: Markus <974709+Links2004@users.noreply.github.com> Date: Thu, 5 Sep 2024 02:57:44 +0200 Subject: [PATCH 53/55] Add StatsD component (#6642) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/statsd/__init__.py | 65 ++++++++ esphome/components/statsd/statsd.cpp | 156 ++++++++++++++++++ esphome/components/statsd/statsd.h | 86 ++++++++++ tests/components/statsD/common.yaml | 29 ++++ tests/components/statsD/test.bk72xx-ard.yaml | 2 + tests/components/statsD/test.esp32-ard.yaml | 2 + .../components/statsD/test.esp32-c3-ard.yaml | 2 + .../components/statsD/test.esp32-c3-idf.yaml | 2 + tests/components/statsD/test.esp32-idf.yaml | 2 + tests/components/statsD/test.esp8266-ard.yaml | 2 + tests/components/statsD/test.rp2040-ard.yaml | 2 + 12 files changed, 351 insertions(+) create mode 100644 esphome/components/statsd/__init__.py create mode 100644 esphome/components/statsd/statsd.cpp create mode 100644 esphome/components/statsd/statsd.h create mode 100644 tests/components/statsD/common.yaml create mode 100644 tests/components/statsD/test.bk72xx-ard.yaml create mode 100644 tests/components/statsD/test.esp32-ard.yaml create mode 100644 tests/components/statsD/test.esp32-c3-ard.yaml create mode 100644 tests/components/statsD/test.esp32-c3-idf.yaml create mode 100644 tests/components/statsD/test.esp32-idf.yaml create mode 100644 tests/components/statsD/test.esp8266-ard.yaml create mode 100644 tests/components/statsD/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 8c706fa2d6..52b5f48a34 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -385,6 +385,7 @@ esphome/components/st7701s/* @clydebarrow esphome/components/st7735/* @SenexCrenshaw esphome/components/st7789v/* @kbx81 esphome/components/st7920/* @marsjan155 +esphome/components/statsd/* @Links2004 esphome/components/substitutions/* @esphome/core esphome/components/sun/* @OttoWinter esphome/components/sun_gtil2/* @Mat931 diff --git a/esphome/components/statsd/__init__.py b/esphome/components/statsd/__init__.py new file mode 100644 index 0000000000..3623338aec --- /dev/null +++ b/esphome/components/statsd/__init__.py @@ -0,0 +1,65 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, binary_sensor +from esphome.const import ( + CONF_ID, + CONF_PORT, + CONF_NAME, + CONF_SENSORS, + CONF_BINARY_SENSORS, +) + +AUTO_LOAD = ["socket"] +CODEOWNERS = ["@Links2004"] +DEPENDENCIES = ["network"] + +CONF_HOST = "host" +CONF_PREFIX = "prefix" + +statsd_component_ns = cg.esphome_ns.namespace("statsd") +StatsdComponent = statsd_component_ns.class_("StatsdComponent", cg.PollingComponent) + +CONFIG_SENSORS_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(sensor.Sensor), + cv.Required(CONF_NAME): cv.string_strict, + } +) + +CONFIG_BINARY_SENSORS_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(binary_sensor.BinarySensor), + cv.Required(CONF_NAME): cv.string_strict, + } +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(StatsdComponent), + cv.Required(CONF_HOST): cv.string_strict, + cv.Optional(CONF_PORT, default=8125): cv.port, + cv.Optional(CONF_PREFIX, default=""): cv.string_strict, + cv.Optional(CONF_SENSORS): cv.ensure_list(CONFIG_SENSORS_SCHEMA), + cv.Optional(CONF_BINARY_SENSORS): cv.ensure_list(CONFIG_BINARY_SENSORS_SCHEMA), + } +).extend(cv.polling_component_schema("10s")) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + cg.add( + var.configure( + config.get(CONF_HOST), + config.get(CONF_PORT), + config.get(CONF_PREFIX), + ) + ) + + for sensor_cfg in config.get(CONF_SENSORS, []): + s = await cg.get_variable(sensor_cfg[CONF_ID]) + cg.add(var.register_sensor(sensor_cfg[CONF_NAME], s)) + + for sensor_cfg in config.get(CONF_BINARY_SENSORS, []): + s = await cg.get_variable(sensor_cfg[CONF_ID]) + cg.add(var.register_binary_sensor(sensor_cfg[CONF_NAME], s)) diff --git a/esphome/components/statsd/statsd.cpp b/esphome/components/statsd/statsd.cpp new file mode 100644 index 0000000000..68b24908d2 --- /dev/null +++ b/esphome/components/statsd/statsd.cpp @@ -0,0 +1,156 @@ +#include "esphome/core/log.h" + +#include "statsd.h" + +namespace esphome { +namespace statsd { + +// send UDP packet if we reach 1Kb packed size +// this is needed since statsD does not support fragmented UDP packets +static const uint16_t SEND_THRESHOLD = 1024; + +static const char *const TAG = "statsD"; + +void StatsdComponent::setup() { +#ifndef USE_ESP8266 + this->sock_ = esphome::socket::socket(AF_INET, SOCK_DGRAM, 0); + + struct sockaddr_in source; + source.sin_family = AF_INET; + source.sin_addr.s_addr = htonl(INADDR_ANY); + source.sin_port = htons(this->port_); + this->sock_->bind((struct sockaddr *) &source, sizeof(source)); + + this->destination_.sin_family = AF_INET; + this->destination_.sin_port = htons(this->port_); + this->destination_.sin_addr.s_addr = inet_addr(this->host_); +#endif +} + +StatsdComponent::~StatsdComponent() { +#ifndef USE_ESP8266 + if (!this->sock_) { + return; + } + this->sock_->close(); +#endif +} + +void StatsdComponent::dump_config() { + ESP_LOGCONFIG(TAG, "statsD:"); + ESP_LOGCONFIG(TAG, " host: %s", this->host_); + ESP_LOGCONFIG(TAG, " port: %d", this->port_); + if (this->prefix_) { + ESP_LOGCONFIG(TAG, " prefix: %s", this->prefix_); + } + + ESP_LOGCONFIG(TAG, " metrics:"); + for (sensors_t s : this->sensors_) { + ESP_LOGCONFIG(TAG, " - name: %s", s.name); + ESP_LOGCONFIG(TAG, " type: %d", s.type); + } +} + +float StatsdComponent::get_setup_priority() const { return esphome::setup_priority::AFTER_WIFI; } + +#ifdef USE_SENSOR +void StatsdComponent::register_sensor(const char *name, esphome::sensor::Sensor *sensor) { + sensors_t s; + s.name = name; + s.sensor = sensor; + s.type = TYPE_SENSOR; + this->sensors_.push_back(s); +} +#endif + +#ifdef USE_BINARY_SENSOR +void StatsdComponent::register_binary_sensor(const char *name, esphome::binary_sensor::BinarySensor *binary_sensor) { + sensors_t s; + s.name = name; + s.binary_sensor = binary_sensor; + s.type = TYPE_BINARY_SENSOR; + this->sensors_.push_back(s); +} +#endif + +void StatsdComponent::update() { + std::string out; + out.reserve(SEND_THRESHOLD); + + for (sensors_t s : this->sensors_) { + double val = 0; + switch (s.type) { +#ifdef USE_SENSOR + case TYPE_SENSOR: + if (!s.sensor->has_state()) { + continue; + } + val = s.sensor->state; + break; +#endif +#ifdef USE_BINARY_SENSOR + case TYPE_BINARY_SENSOR: + if (!s.binary_sensor->has_state()) { + continue; + } + // map bool to double + if (s.binary_sensor->state) { + val = 1; + } + break; +#endif + default: + ESP_LOGE(TAG, "type not known, name: %s type: %d", s.name, s.type); + continue; + } + + // statsD gauge: + // https://github.com/statsd/statsd/blob/master/docs/metric_types.md + // This implies you can't explicitly set a gauge to a negative number without first setting it to zero. + if (val < 0) { + if (this->prefix_) { + out.append(str_sprintf("%s.", this->prefix_)); + } + out.append(str_sprintf("%s:0|g\n", s.name)); + } + if (this->prefix_) { + out.append(str_sprintf("%s.", this->prefix_)); + } + out.append(str_sprintf("%s:%f|g\n", s.name, val)); + + if (out.length() > SEND_THRESHOLD) { + this->send_(&out); + out.clear(); + } + } + + this->send_(&out); +} + +void StatsdComponent::send_(std::string *out) { + if (out->empty()) { + return; + } +#ifdef USE_ESP8266 + IPAddress ip; + ip.fromString(this->host_); + + this->sock_.beginPacket(ip, this->port_); + this->sock_.write((const uint8_t *) out->c_str(), out->length()); + this->sock_.endPacket(); + +#else + if (!this->sock_) { + return; + } + + int n_bytes = this->sock_->sendto(out->c_str(), out->length(), 0, reinterpret_cast(&this->destination_), + sizeof(this->destination_)); + if (n_bytes != out->length()) { + ESP_LOGE(TAG, "Failed to send UDP packed (%d of %d)", n_bytes, out->length()); + } +#endif +} + +} // namespace statsd +} // namespace esphome diff --git a/esphome/components/statsd/statsd.h b/esphome/components/statsd/statsd.h new file mode 100644 index 0000000000..ef42579587 --- /dev/null +++ b/esphome/components/statsd/statsd.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include "esphome/core/defines.h" +#include "esphome/core/component.h" +#include "esphome/components/socket/socket.h" +#include "esphome/components/network/ip_address.h" + +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif + +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + +#ifdef USE_ESP8266 +#include "WiFiUdp.h" +#include "IPAddress.h" +#endif + +namespace esphome { +namespace statsd { + +using sensor_type_t = enum { TYPE_SENSOR, TYPE_BINARY_SENSOR }; + +using sensors_t = struct { + const char *name; + sensor_type_t type; + union { +#ifdef USE_SENSOR + esphome::sensor::Sensor *sensor; +#endif +#ifdef USE_BINARY_SENSOR + esphome::binary_sensor::BinarySensor *binary_sensor; +#endif + }; +}; + +class StatsdComponent : public PollingComponent { + public: + ~StatsdComponent(); + + void setup() override; + void dump_config() override; + void update() override; + float get_setup_priority() const override; + + void configure(const char *host, uint16_t port, const char *prefix) { + this->host_ = host; + this->port_ = port; + this->prefix_ = prefix; + } + +#ifdef USE_SENSOR + void register_sensor(const char *name, esphome::sensor::Sensor *sensor); +#endif + +#ifdef USE_BINARY_SENSOR + void register_binary_sensor(const char *name, esphome::binary_sensor::BinarySensor *binary_sensor); +#endif + + private: + const char *host_; + const char *prefix_; + uint16_t port_; + + std::vector sensors_; + +#ifdef USE_ESP8266 + WiFiUDP sock_; +#else + std::unique_ptr sock_; + struct sockaddr_in destination_; +#endif + + void send_(std::string *out); +}; + +} // namespace statsd +} // namespace esphome diff --git a/tests/components/statsD/common.yaml b/tests/components/statsD/common.yaml new file mode 100644 index 0000000000..5878101de8 --- /dev/null +++ b/tests/components/statsD/common.yaml @@ -0,0 +1,29 @@ +wifi: + ssid: MySSID + password: password1 + +statsd: + host: "192.168.1.1" + port: 8125 + prefix: esphome + update_interval: 60s + sensors: + id: s + name: sensors + binary_sensors: + id: bs + name: binary_sensors + +sensor: + - platform: template + id: s + name: "42.1" + lambda: |- + return 42.1f; + +binary_sensor: + - platform: template + id: bs + name: "On" + lambda: |- + return true; diff --git a/tests/components/statsD/test.bk72xx-ard.yaml b/tests/components/statsD/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/statsD/test.bk72xx-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/statsD/test.esp32-ard.yaml b/tests/components/statsD/test.esp32-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/statsD/test.esp32-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/statsD/test.esp32-c3-ard.yaml b/tests/components/statsD/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/statsD/test.esp32-c3-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/statsD/test.esp32-c3-idf.yaml b/tests/components/statsD/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/statsD/test.esp32-c3-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/statsD/test.esp32-idf.yaml b/tests/components/statsD/test.esp32-idf.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/statsD/test.esp32-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/statsD/test.esp8266-ard.yaml b/tests/components/statsD/test.esp8266-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/statsD/test.esp8266-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/statsD/test.rp2040-ard.yaml b/tests/components/statsD/test.rp2040-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/statsD/test.rp2040-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml From 1548fa0811144302fc9700a50bf8966b7a7d9157 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:09:49 +1200 Subject: [PATCH 54/55] [homeassistant-switch] Support different entity domains (#7331) --- esphome/components/homeassistant/__init__.py | 13 ++++++++++++ .../homeassistant/switch/__init__.py | 17 +++++++++++++-- .../switch/homeassistant_switch.cpp | 4 ++-- tests/components/homeassistant/common.yaml | 21 +++++++++++++++++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/esphome/components/homeassistant/__init__.py b/esphome/components/homeassistant/__init__.py index 6d997e48ca..223d6c18c3 100644 --- a/esphome/components/homeassistant/__init__.py +++ b/esphome/components/homeassistant/__init__.py @@ -5,6 +5,19 @@ from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL CODEOWNERS = ["@OttoWinter", "@esphome/core"] homeassistant_ns = cg.esphome_ns.namespace("homeassistant") + +def validate_entity_domain(platform, supported_domains): + def validator(config): + domain = config[CONF_ENTITY_ID].split(".", 1)[0] + if domain not in supported_domains: + raise cv.Invalid( + f"Entity ID {config[CONF_ENTITY_ID]} is not supported by the {platform} platform." + ) + return config + + return validator + + HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema( { cv.Required(CONF_ENTITY_ID): cv.entity_id, diff --git a/esphome/components/homeassistant/switch/__init__.py b/esphome/components/homeassistant/switch/__init__.py index 3d7c80682a..384f82bbad 100644 --- a/esphome/components/homeassistant/switch/__init__.py +++ b/esphome/components/homeassistant/switch/__init__.py @@ -7,19 +7,32 @@ from .. import ( HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, homeassistant_ns, setup_home_assistant_entity, + validate_entity_domain, ) CODEOWNERS = ["@Links2004"] DEPENDENCIES = ["api"] +SUPPORTED_DOMAINS = [ + "automation", + "fan", + "humidifier", + "input_boolean", + "light", + "remote", + "siren", + "switch", +] + HomeassistantSwitch = homeassistant_ns.class_( "HomeassistantSwitch", switch.Switch, cg.Component ) -CONFIG_SCHEMA = ( +CONFIG_SCHEMA = cv.All( switch.switch_schema(HomeassistantSwitch) - .extend(cv.COMPONENT_SCHEMA) .extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA) + .extend(cv.COMPONENT_SCHEMA), + validate_entity_domain("switch", SUPPORTED_DOMAINS), ) diff --git a/esphome/components/homeassistant/switch/homeassistant_switch.cpp b/esphome/components/homeassistant/switch/homeassistant_switch.cpp index 05ef46e30e..0451c95069 100644 --- a/esphome/components/homeassistant/switch/homeassistant_switch.cpp +++ b/esphome/components/homeassistant/switch/homeassistant_switch.cpp @@ -42,9 +42,9 @@ void HomeassistantSwitch::write_state(bool state) { api::HomeassistantServiceResponse resp; if (state) { - resp.service = "switch.turn_on"; + resp.service = "homeassistant.turn_on"; } else { - resp.service = "switch.turn_off"; + resp.service = "homeassistant.turn_off"; } api::HomeassistantServiceMap entity_id_kv; diff --git a/tests/components/homeassistant/common.yaml b/tests/components/homeassistant/common.yaml index 8c9a4ad75f..9c6cb71b8b 100644 --- a/tests/components/homeassistant/common.yaml +++ b/tests/components/homeassistant/common.yaml @@ -33,6 +33,27 @@ wifi: api: switch: + - platform: homeassistant + entity_id: automation.my_cool_automation + id: my_cool_automation + - platform: homeassistant + entity_id: fan.my_cool_fan + id: my_cool_fan + - platform: homeassistant + entity_id: humidifier.my_cool_humidifier + id: my_cool_humidifier + - platform: homeassistant + entity_id: input_boolean.my_cool_input_boolean + id: my_cool_input_boolean + - platform: homeassistant + entity_id: light.my_cool_light + id: my_cool_light + - platform: homeassistant + entity_id: remote.my_cool_remote + id: my_cool_remote + - platform: homeassistant + entity_id: siren.my_cool_siren + id: my_cool_siren - platform: homeassistant entity_id: switch.my_cool_switch id: my_cool_switch From 18a1191e03578fb395156cb2578dc2f41d2a44f5 Mon Sep 17 00:00:00 2001 From: Adam DeMuri Date: Wed, 4 Sep 2024 23:08:02 -0600 Subject: [PATCH 55/55] Add support for using BMP280 with SPI (#7053) Co-authored-by: Keith Burzinski --- CODEOWNERS | 3 + esphome/components/bmp280/sensor.py | 97 +------------------ esphome/components/bmp280_base/__init__.py | 88 +++++++++++++++++ .../bmp280_base.cpp} | 15 ++- .../bmp280.h => bmp280_base/bmp280_base.h} | 14 ++- esphome/components/bmp280_i2c/__init__.py | 0 esphome/components/bmp280_i2c/bmp280_i2c.cpp | 27 ++++++ esphome/components/bmp280_i2c/bmp280_i2c.h | 22 +++++ esphome/components/bmp280_i2c/sensor.py | 22 +++++ esphome/components/bmp280_spi/__init__.py | 0 esphome/components/bmp280_spi/bmp280_spi.cpp | 65 +++++++++++++ esphome/components/bmp280_spi/bmp280_spi.h | 20 ++++ esphome/components/bmp280_spi/sensor.py | 22 +++++ tests/components/bmp280/test.esp32-ard.yaml | 15 --- .../components/bmp280/test.esp32-c3-ard.yaml | 15 --- tests/components/bmp280/test.esp32-idf.yaml | 15 --- tests/components/bmp280/test.esp8266-ard.yaml | 15 --- tests/components/bmp280/test.rp2040-ard.yaml | 15 --- .../common.yaml} | 10 +- .../components/bmp280_i2c/test.esp32-ard.yaml | 5 + .../bmp280_i2c/test.esp32-c3-ard.yaml | 5 + .../bmp280_i2c/test.esp32-c3-idf.yaml | 5 + .../components/bmp280_i2c/test.esp32-idf.yaml | 5 + .../bmp280_i2c/test.esp8266-ard.yaml | 5 + .../bmp280_i2c/test.rp2040-ard.yaml | 5 + tests/components/bmp280_spi/common.yaml | 18 ++++ .../components/bmp280_spi/test.esp32-ard.yaml | 7 ++ .../bmp280_spi/test.esp32-c3-ard.yaml | 7 ++ .../bmp280_spi/test.esp32-c3-idf.yaml | 7 ++ .../components/bmp280_spi/test.esp32-idf.yaml | 7 ++ .../bmp280_spi/test.esp8266-ard.yaml | 7 ++ .../bmp280_spi/test.rp2040-ard.yaml | 7 ++ 32 files changed, 388 insertions(+), 182 deletions(-) create mode 100644 esphome/components/bmp280_base/__init__.py rename esphome/components/{bmp280/bmp280.cpp => bmp280_base/bmp280_base.cpp} (95%) rename esphome/components/{bmp280/bmp280.h => bmp280_base/bmp280_base.h} (88%) create mode 100644 esphome/components/bmp280_i2c/__init__.py create mode 100644 esphome/components/bmp280_i2c/bmp280_i2c.cpp create mode 100644 esphome/components/bmp280_i2c/bmp280_i2c.h create mode 100644 esphome/components/bmp280_i2c/sensor.py create mode 100644 esphome/components/bmp280_spi/__init__.py create mode 100644 esphome/components/bmp280_spi/bmp280_spi.cpp create mode 100644 esphome/components/bmp280_spi/bmp280_spi.h create mode 100644 esphome/components/bmp280_spi/sensor.py delete mode 100644 tests/components/bmp280/test.esp32-ard.yaml delete mode 100644 tests/components/bmp280/test.esp32-c3-ard.yaml delete mode 100644 tests/components/bmp280/test.esp32-idf.yaml delete mode 100644 tests/components/bmp280/test.esp8266-ard.yaml delete mode 100644 tests/components/bmp280/test.rp2040-ard.yaml rename tests/components/{bmp280/test.esp32-c3-idf.yaml => bmp280_i2c/common.yaml} (56%) create mode 100644 tests/components/bmp280_i2c/test.esp32-ard.yaml create mode 100644 tests/components/bmp280_i2c/test.esp32-c3-ard.yaml create mode 100644 tests/components/bmp280_i2c/test.esp32-c3-idf.yaml create mode 100644 tests/components/bmp280_i2c/test.esp32-idf.yaml create mode 100644 tests/components/bmp280_i2c/test.esp8266-ard.yaml create mode 100644 tests/components/bmp280_i2c/test.rp2040-ard.yaml create mode 100644 tests/components/bmp280_spi/common.yaml create mode 100644 tests/components/bmp280_spi/test.esp32-ard.yaml create mode 100644 tests/components/bmp280_spi/test.esp32-c3-ard.yaml create mode 100644 tests/components/bmp280_spi/test.esp32-c3-idf.yaml create mode 100644 tests/components/bmp280_spi/test.esp32-idf.yaml create mode 100644 tests/components/bmp280_spi/test.esp8266-ard.yaml create mode 100644 tests/components/bmp280_spi/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 52b5f48a34..1d4df3ccb8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -70,6 +70,9 @@ esphome/components/bme680_bsec/* @trvrnrth esphome/components/bme68x_bsec2/* @kbx81 @neffs esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs esphome/components/bmi160/* @flaviut +esphome/components/bmp280_base/* @ademuri +esphome/components/bmp280_i2c/* @ademuri +esphome/components/bmp280_spi/* @ademuri esphome/components/bmp3xx/* @latonita esphome/components/bmp3xx_base/* @latonita @martgras esphome/components/bmp3xx_i2c/* @latonita diff --git a/esphome/components/bmp280/sensor.py b/esphome/components/bmp280/sensor.py index a23bc0766a..a624889982 100644 --- a/esphome/components/bmp280/sensor.py +++ b/esphome/components/bmp280/sensor.py @@ -1,96 +1,5 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor -from esphome.const import ( - CONF_ID, - CONF_PRESSURE, - CONF_TEMPERATURE, - DEVICE_CLASS_PRESSURE, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, - UNIT_CELSIUS, - UNIT_HECTOPASCAL, - CONF_IIR_FILTER, - CONF_OVERSAMPLING, + +CONFIG_SCHEMA = cv.invalid( + "The bmp280 sensor component has been renamed to bmp280_i2c." ) - -DEPENDENCIES = ["i2c"] - -bmp280_ns = cg.esphome_ns.namespace("bmp280") -BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling") -OVERSAMPLING_OPTIONS = { - "NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE, - "1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X, - "2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X, - "4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X, - "8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X, - "16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X, -} - -BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter") -IIR_FILTER_OPTIONS = { - "OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF, - "2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X, - "4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X, - "8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X, - "16X": BMP280IIRFilter.BMP280_IIR_FILTER_16X, -} - -BMP280Component = bmp280_ns.class_( - "BMP280Component", cg.PollingComponent, i2c.I2CDevice -) - -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(BMP280Component), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - accuracy_decimals=1, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( - OVERSAMPLING_OPTIONS, upper=True - ), - } - ), - cv.Optional(CONF_PRESSURE): sensor.sensor_schema( - unit_of_measurement=UNIT_HECTOPASCAL, - accuracy_decimals=1, - device_class=DEVICE_CLASS_PRESSURE, - state_class=STATE_CLASS_MEASUREMENT, - ).extend( - { - cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( - OVERSAMPLING_OPTIONS, upper=True - ), - } - ), - cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( - IIR_FILTER_OPTIONS, upper=True - ), - } - ) - .extend(cv.polling_component_schema("60s")) - .extend(i2c.i2c_device_schema(0x77)) -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await i2c.register_i2c_device(var, config) - - if temperature_config := config.get(CONF_TEMPERATURE): - sens = await sensor.new_sensor(temperature_config) - cg.add(var.set_temperature_sensor(sens)) - cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING])) - - if pressure_config := config.get(CONF_PRESSURE): - sens = await sensor.new_sensor(pressure_config) - cg.add(var.set_pressure_sensor(sens)) - cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING])) - - cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) diff --git a/esphome/components/bmp280_base/__init__.py b/esphome/components/bmp280_base/__init__.py new file mode 100644 index 0000000000..c0f9af9dd7 --- /dev/null +++ b/esphome/components/bmp280_base/__init__.py @@ -0,0 +1,88 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_ID, + CONF_IIR_FILTER, + CONF_OVERSAMPLING, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, +) + +CODEOWNERS = ["@ademuri"] + +bmp280_ns = cg.esphome_ns.namespace("bmp280_base") +BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling") +OVERSAMPLING_OPTIONS = { + "NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE, + "1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X, + "2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X, + "4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X, + "8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X, + "16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X, +} + +BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter") +IIR_FILTER_OPTIONS = { + "OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF, + "2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X, + "4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X, + "8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X, + "16X": BMP280IIRFilter.BMP280_IIR_FILTER_16X, +} + +CONFIG_SCHEMA_BASE = cv.Schema( + { + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( + IIR_FILTER_OPTIONS, upper=True + ), + } +).extend(cv.polling_component_schema("60s")) + + +async def to_code_base(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING])) + + if pressure_config := config.get(CONF_PRESSURE): + sens = await sensor.new_sensor(pressure_config) + cg.add(var.set_pressure_sensor(sens)) + cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING])) + + cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) + + return var diff --git a/esphome/components/bmp280/bmp280.cpp b/esphome/components/bmp280_base/bmp280_base.cpp similarity index 95% rename from esphome/components/bmp280/bmp280.cpp rename to esphome/components/bmp280_base/bmp280_base.cpp index c92daa07fb..f94456f6e6 100644 --- a/esphome/components/bmp280/bmp280.cpp +++ b/esphome/components/bmp280_base/bmp280_base.cpp @@ -1,9 +1,9 @@ -#include "bmp280.h" +#include "bmp280_base.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome { -namespace bmp280 { +namespace bmp280_base { static const char *const TAG = "bmp280.sensor"; @@ -59,6 +59,14 @@ static const char *iir_filter_to_str(BMP280IIRFilter filter) { void BMP280Component::setup() { ESP_LOGCONFIG(TAG, "Setting up BMP280..."); uint8_t chip_id = 0; + + // Read the chip id twice, to work around a bug where the first read is 0. + // https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855 + if (!this->read_byte(0xD0, &chip_id)) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } if (!this->read_byte(0xD0, &chip_id)) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); @@ -122,7 +130,6 @@ void BMP280Component::setup() { } void BMP280Component::dump_config() { ESP_LOGCONFIG(TAG, "BMP280:"); - LOG_I2C_DEVICE(this); switch (this->error_code_) { case COMMUNICATION_FAILED: ESP_LOGE(TAG, "Communication with BMP280 failed!"); @@ -262,5 +269,5 @@ uint16_t BMP280Component::read_u16_le_(uint8_t a_register) { } int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); } -} // namespace bmp280 +} // namespace bmp280_base } // namespace esphome diff --git a/esphome/components/bmp280/bmp280.h b/esphome/components/bmp280_base/bmp280_base.h similarity index 88% rename from esphome/components/bmp280/bmp280.h rename to esphome/components/bmp280_base/bmp280_base.h index 96eb470155..4b22e98f13 100644 --- a/esphome/components/bmp280/bmp280.h +++ b/esphome/components/bmp280_base/bmp280_base.h @@ -2,10 +2,9 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" namespace esphome { -namespace bmp280 { +namespace bmp280_base { /// Internal struct storing the calibration values of an BMP280. struct BMP280CalibrationData { @@ -50,8 +49,8 @@ enum BMP280IIRFilter { BMP280_IIR_FILTER_16X = 0b100, }; -/// This class implements support for the BMP280 Temperature+Pressure i2c sensor. -class BMP280Component : public PollingComponent, public i2c::I2CDevice { +/// This class implements support for the BMP280 Temperature+Pressure sensor. +class BMP280Component : public PollingComponent { public: void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } @@ -68,6 +67,11 @@ class BMP280Component : public PollingComponent, public i2c::I2CDevice { float get_setup_priority() const override; void update() override; + virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0; + virtual bool write_byte(uint8_t a_register, uint8_t data) = 0; + virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0; + virtual bool read_byte_16(uint8_t a_register, uint16_t *data) = 0; + protected: /// Read the temperature value and store the calculated ambient temperature in t_fine. float read_temperature_(int32_t *t_fine); @@ -90,5 +94,5 @@ class BMP280Component : public PollingComponent, public i2c::I2CDevice { } error_code_{NONE}; }; -} // namespace bmp280 +} // namespace bmp280_base } // namespace esphome diff --git a/esphome/components/bmp280_i2c/__init__.py b/esphome/components/bmp280_i2c/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/bmp280_i2c/bmp280_i2c.cpp b/esphome/components/bmp280_i2c/bmp280_i2c.cpp new file mode 100644 index 0000000000..04b8bd8b10 --- /dev/null +++ b/esphome/components/bmp280_i2c/bmp280_i2c.cpp @@ -0,0 +1,27 @@ +#include "bmp280_i2c.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace bmp280_i2c { + +bool BMP280I2CComponent::read_byte(uint8_t a_register, uint8_t *data) { + return I2CDevice::read_byte(a_register, data); +}; +bool BMP280I2CComponent::write_byte(uint8_t a_register, uint8_t data) { + return I2CDevice::write_byte(a_register, data); +}; +bool BMP280I2CComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) { + return I2CDevice::read_bytes(a_register, data, len); +}; +bool BMP280I2CComponent::read_byte_16(uint8_t a_register, uint16_t *data) { + return I2CDevice::read_byte_16(a_register, data); +}; + +void BMP280I2CComponent::dump_config() { + LOG_I2C_DEVICE(this); + BMP280Component::dump_config(); +} + +} // namespace bmp280_i2c +} // namespace esphome diff --git a/esphome/components/bmp280_i2c/bmp280_i2c.h b/esphome/components/bmp280_i2c/bmp280_i2c.h new file mode 100644 index 0000000000..66d78d788b --- /dev/null +++ b/esphome/components/bmp280_i2c/bmp280_i2c.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/components/bmp280_base/bmp280_base.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace bmp280_i2c { + +static const char *const TAG = "bmp280_i2c.sensor"; + +/// This class implements support for the BMP280 Temperature+Pressure i2c sensor. +class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice { + public: + bool read_byte(uint8_t a_register, uint8_t *data) override; + bool write_byte(uint8_t a_register, uint8_t data) override; + bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override; + bool read_byte_16(uint8_t a_register, uint16_t *data) override; + void dump_config() override; +}; + +} // namespace bmp280_i2c +} // namespace esphome diff --git a/esphome/components/bmp280_i2c/sensor.py b/esphome/components/bmp280_i2c/sensor.py new file mode 100644 index 0000000000..991bb827a3 --- /dev/null +++ b/esphome/components/bmp280_i2c/sensor.py @@ -0,0 +1,22 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from ..bmp280_base import to_code_base, CONFIG_SCHEMA_BASE + +AUTO_LOAD = ["bmp280_base"] +CODEOWNERS = ["@ademuri"] +DEPENDENCIES = ["i2c"] + +bmp280_ns = cg.esphome_ns.namespace("bmp280_i2c") +BMP280I2CComponent = bmp280_ns.class_( + "BMP280I2CComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( + i2c.i2c_device_schema(default_address=0x77) +).extend({cv.GenerateID(): cv.declare_id(BMP280I2CComponent)}) + + +async def to_code(config): + var = await to_code_base(config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/bmp280_spi/__init__.py b/esphome/components/bmp280_spi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/bmp280_spi/bmp280_spi.cpp b/esphome/components/bmp280_spi/bmp280_spi.cpp new file mode 100644 index 0000000000..a35e829432 --- /dev/null +++ b/esphome/components/bmp280_spi/bmp280_spi.cpp @@ -0,0 +1,65 @@ +#include +#include + +#include "bmp280_spi.h" +#include + +namespace esphome { +namespace bmp280_spi { + +uint8_t set_bit(uint8_t num, uint8_t position) { + uint8_t mask = 1 << position; + return num | mask; +} + +uint8_t clear_bit(uint8_t num, uint8_t position) { + uint8_t mask = 1 << position; + return num & ~mask; +} + +void BMP280SPIComponent::setup() { + this->spi_setup(); + BMP280Component::setup(); +}; + +// In SPI mode, only 7 bits of the register addresses are used; the MSB of register address is not used +// and replaced by a read/write bit (RW = ‘0’ for write and RW = ‘1’ for read). +// Example: address 0xF7 is accessed by using SPI register address 0x77. For write access, the byte +// 0x77 is transferred, for read access, the byte 0xF7 is transferred. +// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf + +bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) { + this->enable(); + this->transfer_byte(set_bit(a_register, 7)); + *data = this->transfer_byte(0); + this->disable(); + return true; +} + +bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) { + this->enable(); + this->transfer_byte(clear_bit(a_register, 7)); + this->transfer_byte(data); + this->disable(); + return true; +} + +bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) { + this->enable(); + this->transfer_byte(set_bit(a_register, 7)); + this->read_array(data, len); + this->disable(); + return true; +} + +bool BMP280SPIComponent::read_byte_16(uint8_t a_register, uint16_t *data) { + this->enable(); + this->transfer_byte(set_bit(a_register, 7)); + ((uint8_t *) data)[1] = this->transfer_byte(0); + ((uint8_t *) data)[0] = this->transfer_byte(0); + this->disable(); + return true; +} + +} // namespace bmp280_spi +} // namespace esphome diff --git a/esphome/components/bmp280_spi/bmp280_spi.h b/esphome/components/bmp280_spi/bmp280_spi.h new file mode 100644 index 0000000000..dd226502f6 --- /dev/null +++ b/esphome/components/bmp280_spi/bmp280_spi.h @@ -0,0 +1,20 @@ +#pragma once + +#include "esphome/components/bmp280_base/bmp280_base.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace bmp280_spi { + +class BMP280SPIComponent : public esphome::bmp280_base::BMP280Component, + public spi::SPIDevice { + void setup() override; + bool read_byte(uint8_t a_register, uint8_t *data) override; + bool write_byte(uint8_t a_register, uint8_t data) override; + bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override; + bool read_byte_16(uint8_t a_register, uint16_t *data) override; +}; + +} // namespace bmp280_spi +} // namespace esphome diff --git a/esphome/components/bmp280_spi/sensor.py b/esphome/components/bmp280_spi/sensor.py new file mode 100644 index 0000000000..511d45b24e --- /dev/null +++ b/esphome/components/bmp280_spi/sensor.py @@ -0,0 +1,22 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import spi +from ..bmp280_base import to_code_base, CONFIG_SCHEMA_BASE + +AUTO_LOAD = ["bmp280_base"] +CODEOWNERS = ["@ademuri"] +DEPENDENCIES = ["spi"] + +bmp280_ns = cg.esphome_ns.namespace("bmp280_spi") +BMP280SPIComponent = bmp280_ns.class_( + "BMP280SPIComponent", cg.PollingComponent, spi.SPIDevice +) + +CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( + spi.spi_device_schema(default_mode="mode3") +).extend({cv.GenerateID(): cv.declare_id(BMP280SPIComponent)}) + + +async def to_code(config): + var = await to_code_base(config) + await spi.register_spi_device(var, config) diff --git a/tests/components/bmp280/test.esp32-ard.yaml b/tests/components/bmp280/test.esp32-ard.yaml deleted file mode 100644 index aeb1cb262b..0000000000 --- a/tests/components/bmp280/test.esp32-ard.yaml +++ /dev/null @@ -1,15 +0,0 @@ -i2c: - - id: i2c_bmp280 - scl: 16 - sda: 17 - -sensor: - - platform: bmp280 - address: 0x77 - temperature: - name: Outside Temperature - oversampling: 16x - pressure: - name: Outside Pressure - iir_filter: 16x - update_interval: 15s diff --git a/tests/components/bmp280/test.esp32-c3-ard.yaml b/tests/components/bmp280/test.esp32-c3-ard.yaml deleted file mode 100644 index 5f7f85d3e2..0000000000 --- a/tests/components/bmp280/test.esp32-c3-ard.yaml +++ /dev/null @@ -1,15 +0,0 @@ -i2c: - - id: i2c_bmp280 - scl: 5 - sda: 4 - -sensor: - - platform: bmp280 - address: 0x77 - temperature: - name: Outside Temperature - oversampling: 16x - pressure: - name: Outside Pressure - iir_filter: 16x - update_interval: 15s diff --git a/tests/components/bmp280/test.esp32-idf.yaml b/tests/components/bmp280/test.esp32-idf.yaml deleted file mode 100644 index aeb1cb262b..0000000000 --- a/tests/components/bmp280/test.esp32-idf.yaml +++ /dev/null @@ -1,15 +0,0 @@ -i2c: - - id: i2c_bmp280 - scl: 16 - sda: 17 - -sensor: - - platform: bmp280 - address: 0x77 - temperature: - name: Outside Temperature - oversampling: 16x - pressure: - name: Outside Pressure - iir_filter: 16x - update_interval: 15s diff --git a/tests/components/bmp280/test.esp8266-ard.yaml b/tests/components/bmp280/test.esp8266-ard.yaml deleted file mode 100644 index 5f7f85d3e2..0000000000 --- a/tests/components/bmp280/test.esp8266-ard.yaml +++ /dev/null @@ -1,15 +0,0 @@ -i2c: - - id: i2c_bmp280 - scl: 5 - sda: 4 - -sensor: - - platform: bmp280 - address: 0x77 - temperature: - name: Outside Temperature - oversampling: 16x - pressure: - name: Outside Pressure - iir_filter: 16x - update_interval: 15s diff --git a/tests/components/bmp280/test.rp2040-ard.yaml b/tests/components/bmp280/test.rp2040-ard.yaml deleted file mode 100644 index 5f7f85d3e2..0000000000 --- a/tests/components/bmp280/test.rp2040-ard.yaml +++ /dev/null @@ -1,15 +0,0 @@ -i2c: - - id: i2c_bmp280 - scl: 5 - sda: 4 - -sensor: - - platform: bmp280 - address: 0x77 - temperature: - name: Outside Temperature - oversampling: 16x - pressure: - name: Outside Pressure - iir_filter: 16x - update_interval: 15s diff --git a/tests/components/bmp280/test.esp32-c3-idf.yaml b/tests/components/bmp280_i2c/common.yaml similarity index 56% rename from tests/components/bmp280/test.esp32-c3-idf.yaml rename to tests/components/bmp280_i2c/common.yaml index 5f7f85d3e2..edf52b2cd4 100644 --- a/tests/components/bmp280/test.esp32-c3-idf.yaml +++ b/tests/components/bmp280_i2c/common.yaml @@ -1,15 +1,17 @@ i2c: - id: i2c_bmp280 - scl: 5 - sda: 4 + scl: ${scl_pin} + sda: ${sda_pin} sensor: - - platform: bmp280 + - platform: bmp280_i2c + i2c_id: i2c_bmp280 address: 0x77 temperature: + id: bmp280_temperature name: Outside Temperature - oversampling: 16x pressure: name: Outside Pressure + id: bmp280_pressure iir_filter: 16x update_interval: 15s diff --git a/tests/components/bmp280_i2c/test.esp32-ard.yaml b/tests/components/bmp280_i2c/test.esp32-ard.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/bmp280_i2c/test.esp32-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/bmp280_i2c/test.esp32-c3-ard.yaml b/tests/components/bmp280_i2c/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/bmp280_i2c/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bmp280_i2c/test.esp32-c3-idf.yaml b/tests/components/bmp280_i2c/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/bmp280_i2c/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bmp280_i2c/test.esp32-idf.yaml b/tests/components/bmp280_i2c/test.esp32-idf.yaml new file mode 100644 index 0000000000..63c3bd6afd --- /dev/null +++ b/tests/components/bmp280_i2c/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO16 + sda_pin: GPIO17 + +<<: !include common.yaml diff --git a/tests/components/bmp280_i2c/test.esp8266-ard.yaml b/tests/components/bmp280_i2c/test.esp8266-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/bmp280_i2c/test.esp8266-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bmp280_i2c/test.rp2040-ard.yaml b/tests/components/bmp280_i2c/test.rp2040-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/bmp280_i2c/test.rp2040-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/bmp280_spi/common.yaml b/tests/components/bmp280_spi/common.yaml new file mode 100644 index 0000000000..798804de5b --- /dev/null +++ b/tests/components/bmp280_spi/common.yaml @@ -0,0 +1,18 @@ +spi: + - id: spi_bmp280 + clk_pin: ${clk_pin} + mosi_pin: ${mosi_pin} + miso_pin: ${miso_pin} + +sensor: + - platform: bmp280_spi + spi_id: spi_bmp280 + cs_pin: ${cs_pin} + temperature: + id: bmp280_temperature + name: Outside Temperature + pressure: + name: Outside Pressure + id: bmp280_pressure + iir_filter: 16x + update_interval: 15s diff --git a/tests/components/bmp280_spi/test.esp32-ard.yaml b/tests/components/bmp280_spi/test.esp32-ard.yaml new file mode 100644 index 0000000000..54e027a614 --- /dev/null +++ b/tests/components/bmp280_spi/test.esp32-ard.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO16 + mosi_pin: GPIO17 + miso_pin: GPIO15 + cs_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/bmp280_spi/test.esp32-c3-ard.yaml b/tests/components/bmp280_spi/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..2415ba5dc6 --- /dev/null +++ b/tests/components/bmp280_spi/test.esp32-c3-ard.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO6 + mosi_pin: GPIO7 + miso_pin: GPIO5 + cs_pin: GPIO8 + +<<: !include common.yaml diff --git a/tests/components/bmp280_spi/test.esp32-c3-idf.yaml b/tests/components/bmp280_spi/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..2415ba5dc6 --- /dev/null +++ b/tests/components/bmp280_spi/test.esp32-c3-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO6 + mosi_pin: GPIO7 + miso_pin: GPIO5 + cs_pin: GPIO8 + +<<: !include common.yaml diff --git a/tests/components/bmp280_spi/test.esp32-idf.yaml b/tests/components/bmp280_spi/test.esp32-idf.yaml new file mode 100644 index 0000000000..54e027a614 --- /dev/null +++ b/tests/components/bmp280_spi/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO16 + mosi_pin: GPIO17 + miso_pin: GPIO15 + cs_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/bmp280_spi/test.esp8266-ard.yaml b/tests/components/bmp280_spi/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dbd158d030 --- /dev/null +++ b/tests/components/bmp280_spi/test.esp8266-ard.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO14 + mosi_pin: GPIO13 + miso_pin: GPIO12 + cs_pin: GPIO15 + +<<: !include common.yaml diff --git a/tests/components/bmp280_spi/test.rp2040-ard.yaml b/tests/components/bmp280_spi/test.rp2040-ard.yaml new file mode 100644 index 0000000000..f6c3f1eeca --- /dev/null +++ b/tests/components/bmp280_spi/test.rp2040-ard.yaml @@ -0,0 +1,7 @@ +substitutions: + clk_pin: GPIO2 + mosi_pin: GPIO3 + miso_pin: GPIO4 + cs_pin: GPIO5 + +<<: !include common.yaml